diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000..594b0cce --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,93 @@ +version: 2 + +orbs: + browser-tools: circleci/browser-tools@1.4.0 + +jobs: + build: + working_directory: ~/otb-api + + # Primary container image where all commands run + + docker: + - image: cimg/ruby:3.1.2-browsers + environment: + PGUSER: root + RAILS_ENV: test + DB_HOSTNAME: 127.0.0.1 + DB_USERNAME: user + DB_PASSWORD: password + TEST_DB_NAME: otb + MYSQL_ALLOW_EMPTY_PASSWORD: true + CI: 'circleci' + BROWSER_PATH: /usr/bin/google-chrome + + - image: circleci/postgres:10 + environment: + POSTGRES_USER: user + POSTGRES_DB: otb + POSTGRES_PASSWORD: password + + - image: circleci/mysql:latest + command: [--default-authentication-plugin=mysql_native_password] + environment: + MYSQL_DATABASE: otb + MYSQL_USER: user + MYSQL_PASSWORD: password + MYSQL_ALLOW_EMPTY_PASSWORD: yes + MYSQL_ROOT_PASSWORD: password + + steps: + - checkout + - run: + name: Install dependencies + command: | + wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key add - + sudo apt update + sudo apt install -y postgresql-client || true + sudo apt install -y imagemagick libappindicator1 fonts-liberation + wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb + sudo dpkg -i google-chrome*.deb + gem install bundle + bundle check --path=vendor/bundle || bundle install --path=vendor/bundle --jobs 4 --retry 3 + + - run: + name: Make Tmp Directory + command: | + sudo mkdir -p /data/tmp + sudo chmod 777 /data/tmp + sudo chown $USER:$USER /data/tmp + sudo mkdir -p public/storage/tmp + sudo chmod 777 public/storage/tmp + sudo chown $USER:$USER public/storage/tmp + + # Test building MySQL + # TODO: Figure out how to allow the database user to create new tenants in + # the MySQL Docker instance. For now, we'll just test that it can setup the + # initial database using MySQL. + # - run: + # name: Wait for DB + # command: dockerize -wait tcp://127.0.0.1:3306 -timeout 1m + + # - run: + # name: MySQL Setup + # command: | + # export DB_ADAPTER=mysql2 + # bundle exec rake db:drop RAILS_ENV=test DB_ADAPTER=mysql2 + # bundle exec rake db:create RAILS_ENV=test DB_ADAPTER=mysql2 + # bundle exec rake db:schema:load RAILS_ENV=test DB_ADAPTER=mysql2 + # bundle exec rake db:migrate RAILS_ENV=test DB_ADAPTER=mysql2 + + # Test using PostgreSQL + - run: + name: PostgreSQL Setup + command: | + export DB_ADAPTER=postgresql + bundle exec rake db:drop RAILS_ENV=test DB_ADAPTER=postgresql + bundle exec rake db:create RAILS_ENV=test DB_ADAPTER=postgresql + bundle exec rake db:schema:load RAILS_ENV=test DB_ADAPTER=postgresql + bundle exec rake db:migrate RAILS_ENV=test DB_ADAPTER=postgresql + + - run: + name: Parallel RSpec with PostgreSQL + command: DB_ADAPTER=postgresql bundle exec rspec spec/ diff --git a/.coveralls.yml b/.coveralls.yml deleted file mode 100644 index 6e649991..00000000 --- a/.coveralls.yml +++ /dev/null @@ -1 +0,0 @@ -service_name: travis-ci \ No newline at end of file diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..93ded0d9 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,54 @@ +# Git +.git +.gitignore + +# Documentation +README.md +CHANGELOG.md +LICENSE + +# Development files +.env* + +# Dependencies +node_modules +vendor/bundle/.bundle + +# Build artifacts +tmp/* +log/* +coverage/* +public/storage/* +storage/* + +# Test files +spec/ +test/ +.rspec + +# Development tools +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Rails specific +/log/*.log +/tmp +/storage +/public/storage +/coverage + +# Docker +Dockerfile* +docker-compose* \ No newline at end of file diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000..4faa2c7f --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,112 @@ +name: Deploy + +on: + push: + branches: [ develop, main ] + workflow_dispatch: + inputs: + environment: + description: 'Environment to deploy to' + required: true + default: 'staging' + type: choice + options: + - staging + - production + +env: + RAILS_ENV: production + +permissions: + id-token: write # Required for AWS OIDC + contents: read + +jobs: + test: + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:13 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: otb_test + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.1.2 + bundler-cache: true + + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y postgresql-client imagemagick libpq-dev gdal-bin libgdal-dev + + - name: Create directories + run: | + sudo mkdir -p /data/tmp + sudo chmod 777 /data/tmp + mkdir -p public/storage/tmp + + - name: Set up database + run: | + export DB_ADAPTER=postgresql + until pg_isready -h localhost -p 5432 -U postgres; do sleep 2; done + bundle exec rake db:create RAILS_ENV=test DB_ADAPTER=postgresql + bundle exec rake db:schema:load RAILS_ENV=test DB_ADAPTER=postgresql + + - name: Run tests + run: | + DB_ADAPTER=postgresql bundle exec rspec --format progress + + deploy: + needs: test + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ secrets.AWS_ROLE }} + aws-region: us-east-1 + role-session-name: GitHub-OIDC-OTB + + - name: Deploy to ECS + env: + AWS_ECR: ${{ secrets.AWS_ECR }} + AWS_ECS_CLUSTER_DEV: ${{ secrets.AWS_ECS_CLUSTER_DEV }} + AWS_ECS_CLUSTER_PROD: ${{ secrets.AWS_ECS_CLUSTER_PROD }} + AWS_ECS_SERVICE_DEV: ${{ secrets.AWS_ECS_SERVICE_DEV }} + AWS_ECS_SERVICE_PROD: ${{ secrets.AWS_ECS_SERVICE_PROD }} + AWS_REGION: ${{ secrets.AWS_REGION }} + BRANCH: ${{ github.ref_name }} + run: | + chmod +x build.sh + ./build.sh + + - name: Notify deployment status + if: always() + run: | + branch="${{ github.ref_name }}" + environment=$([ "$branch" == "main" ] && echo "production" || echo "staging") + if [[ "${{ job.status }}" == "success" ]]; then + echo "Successfully deployed to $environment via ECS" + else + echo "Deployment to $environment failed" + fi \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..55109b48 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,81 @@ +name: Test + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main, develop ] + +env: + RAILS_ENV: test + PGUSER: postgres + DB_HOSTNAME: localhost + DB_USERNAME: postgres + DB_PASSWORD: postgres + TEST_DB_NAME: otb_test + CI: 'github-actions' + BROWSER_PATH: /usr/bin/google-chrome + +jobs: + test: + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:13 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: otb_test + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.1.2 + bundler-cache: true + + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y postgresql-client imagemagick libpq-dev gdal-bin libgdal-dev fonts-liberation + wget -q https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb + sudo dpkg -i google-chrome-stable_current_amd64.deb || true + sudo apt-get install -f -y + + - name: Create required directories + run: | + sudo mkdir -p /data/tmp + sudo chmod 777 /data/tmp + sudo chown $USER:$USER /data/tmp + mkdir -p public/storage/tmp + chmod 777 public/storage/tmp + + - name: Wait for database to be ready + run: | + until pg_isready -h localhost -p 5432 -U postgres; do + echo "Waiting for PostgreSQL..." + sleep 2 + done + + - name: Set up PostgreSQL database + run: | + export DB_ADAPTER=postgresql + bundle exec rake db:drop RAILS_ENV=test DB_ADAPTER=postgresql || true + bundle exec rake db:create RAILS_ENV=test DB_ADAPTER=postgresql + bundle exec rake db:schema:load RAILS_ENV=test DB_ADAPTER=postgresql + bundle exec rake db:migrate RAILS_ENV=test DB_ADAPTER=postgresql + + - name: Run RSpec tests + run: | + DB_ADAPTER=postgresql bundle exec rspec --format progress \ No newline at end of file diff --git a/.gitignore b/.gitignore index e95e6825..8930b17f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,15 @@ *.DS_Store coverage -config/database.yml log config/secrets.yml tmp config/initializers/auth.rb public/uploads/ +public/storage/* .vscode *.env* -.ruby-version +.bundle +.coveralls.yml +/config/master.key +storage diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 00000000..ef538c28 --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +3.1.2 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 35575793..00000000 --- a/.travis.yml +++ /dev/null @@ -1,28 +0,0 @@ -language: ruby -rvm: - - 2.6.3 - -services: - - postgresql - - mysql - -before_install: - - cp config/database.yml.travis config/database.yml - - cp config/secrets.yml.dist config/secrets.yml - - gem install bundler - -env: - - DB=mysql - - DB=postgres - -before_script: - - sudo service postgresql restart - - psql -c 'create database otb_test;' -U postgres - - mysql -e 'create database otb_test' - -script: - - bundle install - - bundle exec rake db:drop - - bundle exec rake db:create - - bundle exec rake db:schema:load - - bundle exec rspec ./spec/requests diff --git a/Capfile b/Capfile index 51956b36..5fad1378 100644 --- a/Capfile +++ b/Capfile @@ -29,7 +29,7 @@ install_plugin Capistrano::SCM::Git # require 'capistrano/rvm' require 'capistrano/rbenv' set :rbenv_type, :user # or :system, depends on your rbenv setup -set :rbenv_ruby, '2.6.3' +set :rbenv_ruby, '3.0.2' # require 'capistrano/chruby' require 'capistrano/bundler' # require 'capistrano/rails/assets' diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..a2610361 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,34 @@ +FROM ruby:3.1.2-slim + +RUN apt-get update -qq && \ + apt-get install -y --no-install-recommends \ + build-essential \ + curl \ + git \ + postgresql-client \ + libpq-dev \ + libgdal-dev \ + gdal-bin \ + imagemagick \ + libmagickwand-dev \ + libvips \ + pkg-config \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /rails + +COPY Gemfile Gemfile.lock ./ + +RUN gem install bundler:2.2.22 +RUN bundle install + +COPY . . + +RUN mkdir -p /data/tmp public/storage/tmp tmp/pids && \ + chmod 777 /data/tmp public/storage/tmp tmp/pids + +EXPOSE 3000 + +RUN chmod +x ./entrypoint.sh + +ENTRYPOINT ["./entrypoint.sh"] \ No newline at end of file diff --git a/Gemfile b/Gemfile index 4d012e24..d948983e 100644 --- a/Gemfile +++ b/Gemfile @@ -9,32 +9,42 @@ end # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' -gem 'rails', '~> 5.2.0' -gem "rack", ">= 2.0.6" +# gem 'rails', '~> 7.0.4' +gem 'rails', '~> 6.1.0' + +gem 'rack', '>= 2.0.6' gem 'pg' gem 'mysql2' # Multitenancy for Rails and ActiveRecord -gem 'apartment' +gem 'ros-apartment', require: 'apartment' # For JSONAPI responses -gem 'active_model_serializers', '~> 0.10.0.rc3' -gem 'acts-as-taggable-on', '~> 5.0' +gem 'active_model_serializers', '~> 0.10.12' +# For pagination +gem 'kaminari' +# gem 'pagy', '~> 5.10' # Use Puma as the app server gem 'puma', '~> 4.3.0' # Use Redis adapter to run Action Cable in production gem 'redis', '~> 3.0' -gem "actionview", ">= 5.2.2.1" +gem 'actionview', '>= 5.2.2.1' # Use ActiveModel has_secure_password # gem 'bcrypt', '~> 3.1.7' -# Social Auth # gem 'ecds_rails_auth_engine', path: '../ecds_auth_engine' -gem 'ecds_rails_auth_engine', git: 'https://github.com/ecds/ecds_rails_auth_engine.git', :tag => 'v0.1.5' -gem 'cancancan', '~> 2.0' +gem 'ecds_rails_auth_engine', git: 'https://github.com/ecds/ecds_rails_auth_engine.git', branch: 'feature/fauxoauth' # Active Storage will land in 5.2 gem 'carrierwave', '~> 1.0' gem 'mini_magick' -gem 'carrierwave-base64' +gem 'image_processing', '~> 1.2' +gem 'ferrum' +gem 'aws-sdk-s3', '~> 1' + +# RGeo is a geospatial data library for Ruby. +# https://github.com/rgeo/rgeo +gem 'rgeo' +gem 'ipinfo-rails' + # Vidoe provider APIs gem 'vimeo' @@ -45,20 +55,20 @@ gem 'youtube_rails' gem 'rack-cors' # TODO: should probably only require this for :test -gem 'faker', git: 'https://github.com/stympy/faker.git', branch: 'master' +gem 'faker' group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console # gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] - gem "test-prof" + # gem "test-prof" end group :development do - gem 'listen', '>= 3.0.5', '< 3.2' + gem 'listen' # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring gem 'spring' gem 'spring-watcher-listen', '~> 2.0.0' - gem 'rspec-rails', '~> 3.5' + gem 'rspec-rails', '~> 5.1.2' # Use Capistrano for deployment gem 'capistrano-rails' gem 'capistrano-rbenv', '~> 2.0' @@ -67,13 +77,19 @@ end group :test do - gem "factory_bot" + gem 'factory_bot' gem 'factory_bot_rails' - gem 'shoulda-matchers', git: 'https://github.com/thoughtbot/shoulda-matchers.git', branch: 'rails-5' + gem 'shoulda-matchers', '~> 4.5.1' #git: 'https://github.com/thoughtbot/shoulda-matchers.git', branch: 'rails-5' gem 'database_cleaner' - gem 'coveralls', require: false gem 'webmock' + gem 'coveralls', require: false + gem 'simplecov', require: false + gem 'simplecov-lcov', require: false + gem 'term-ansicolor' end # Windows does not include zoneinfo files, so bundle the tzinfo-data gem gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] +gem 'net-smtp', require: false +gem 'net-imap', require: false +gem 'net-pop', require: false diff --git a/Gemfile.backup b/Gemfile.backup new file mode 100644 index 00000000..eb885ebc --- /dev/null +++ b/Gemfile.backup @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' + +git_source(:github) do |repo_name| + repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?('/') + "https://github.com/#{repo_name}.git" +end + + +# Bundle edge Rails instead: gem 'rails', github: 'rails/rails' +gem 'rails', '~> 5.2.0' +gem "rack", ">= 2.0.6" +gem 'pg' +gem 'mysql2' +# Multitenancy for Rails and ActiveRecord +gem 'apartment' +# For JSONAPI responses +gem 'active_model_serializers', '~> 0.10.0.rc3' +gem 'acts-as-taggable-on', '~> 5.0' +# Use Puma as the app server +gem 'puma', '~> 4.3.0' +# Use Redis adapter to run Action Cable in production +gem 'redis', '~> 3.0' +gem "actionview", ">= 5.2.2.1" +# Use ActiveModel has_secure_password +# gem 'bcrypt', '~> 3.1.7' + +# Social Auth +# gem 'ecds_rails_auth_engine', path: '../ecds_auth_engine' +gem 'ecds_rails_auth_engine', git: 'https://github.com/ecds/ecds_rails_auth_engine.git', :tag => 'v0.1.5' +gem 'cancancan', '~> 2.0' + +# Active Storage will land in 5.2 +gem 'carrierwave', '~> 1.0' +gem 'mini_magick' +gem 'carrierwave-base64' + +# Vidoe provider APIs +gem 'vimeo' +gem 'yt' +gem 'youtube_rails' + +# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible +gem 'rack-cors' + +# TODO: should probably only require this for :test +gem 'faker', git: 'https://github.com/stympy/faker.git', branch: 'main' + +group :development, :test do + # Call 'byebug' anywhere in the code to stop execution and get a debugger console + # gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] + gem "test-prof" +end + +group :development do + gem 'listen', '>= 3.0.5', '< 3.2' + # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring + gem 'spring' + gem 'spring-watcher-listen', '~> 2.0.0' + gem 'rspec-rails', '~> 3.5' + # Use Capistrano for deployment + gem 'capistrano-rails' + gem 'capistrano-rbenv', '~> 2.0' + gem 'capistrano-passenger' +end + + +group :test do + gem "factory_bot" + gem 'factory_bot_rails' + gem 'shoulda-matchers', git: 'https://github.com/thoughtbot/shoulda-matchers.git', branch: 'rails-5' + gem 'database_cleaner' + gem 'coveralls', require: false + gem 'webmock' +end + +# Windows does not include zoneinfo files, so bundle the tzinfo-data gem +gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] +gem "mimemagic", ">= 0.3.5" diff --git a/Gemfile.lock b/Gemfile.lock index 4a680fd4..48a30662 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,348 +1,438 @@ GIT remote: https://github.com/ecds/ecds_rails_auth_engine.git - revision: 834d77e7ec3a18e21cd490b91ff99770ea0afaad - tag: v0.1.5 + revision: fe56d7d214403689a0ab0ab79cbc9d343fdc63f6 + branch: feature/fauxoauth specs: - ecds_rails_auth_engine (0.1.5) - cancancan (~> 2.0) - rails (~> 5.2.0) - rails_api_auth - -GIT - remote: https://github.com/stympy/faker.git - revision: 7966190d46fd8165b58f3be1bffe41d37ccaea7c - branch: master - specs: - faker (2.10.2) - i18n (>= 1.6, < 2) - -GIT - remote: https://github.com/thoughtbot/shoulda-matchers.git - revision: 4b160bd19ecca7f97d7ac22dccd5fde9b0da5a9f - branch: rails-5 - specs: - shoulda-matchers (3.1.2) - activesupport (>= 4.2.0) + ecds_rails_auth_engine (0.2.0) + cancancan + httparty + jwt + rails GEM remote: https://rubygems.org/ specs: - actioncable (5.2.4.1) - actionpack (= 5.2.4.1) + IPinfo (1.0.1) + faraday (~> 1.0) + json (~> 2.1) + lru_redux (~> 1.1) + actioncable (6.1.6.1) + actionpack (= 6.1.6.1) + activesupport (= 6.1.6.1) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailer (5.2.4.1) - actionpack (= 5.2.4.1) - actionview (= 5.2.4.1) - activejob (= 5.2.4.1) + actionmailbox (6.1.6.1) + actionpack (= 6.1.6.1) + activejob (= 6.1.6.1) + activerecord (= 6.1.6.1) + activestorage (= 6.1.6.1) + activesupport (= 6.1.6.1) + mail (>= 2.7.1) + actionmailer (6.1.6.1) + actionpack (= 6.1.6.1) + actionview (= 6.1.6.1) + activejob (= 6.1.6.1) + activesupport (= 6.1.6.1) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (5.2.4.1) - actionview (= 5.2.4.1) - activesupport (= 5.2.4.1) - rack (~> 2.0, >= 2.0.8) + actionpack (6.1.6.1) + actionview (= 6.1.6.1) + activesupport (= 6.1.6.1) + rack (~> 2.0, >= 2.0.9) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.2.4.1) - activesupport (= 5.2.4.1) + rails-html-sanitizer (~> 1.0, >= 1.2.0) + actiontext (6.1.6.1) + actionpack (= 6.1.6.1) + activerecord (= 6.1.6.1) + activestorage (= 6.1.6.1) + activesupport (= 6.1.6.1) + nokogiri (>= 1.8.5) + actionview (6.1.6.1) + activesupport (= 6.1.6.1) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.0.3) - active_model_serializers (0.10.10) - actionpack (>= 4.1, < 6.1) - activemodel (>= 4.1, < 6.1) + rails-html-sanitizer (~> 1.1, >= 1.2.0) + active_model_serializers (0.10.13) + actionpack (>= 4.1, < 7.1) + activemodel (>= 4.1, < 7.1) case_transform (>= 0.2) jsonapi-renderer (>= 0.1.1.beta1, < 0.3) - activejob (5.2.4.1) - activesupport (= 5.2.4.1) + activejob (6.1.6.1) + activesupport (= 6.1.6.1) globalid (>= 0.3.6) - activemodel (5.2.4.1) - activesupport (= 5.2.4.1) - activerecord (5.2.4.1) - activemodel (= 5.2.4.1) - activesupport (= 5.2.4.1) - arel (>= 9.0) - activestorage (5.2.4.1) - actionpack (= 5.2.4.1) - activerecord (= 5.2.4.1) - marcel (~> 0.3.1) - activesupport (5.2.4.1) + activemodel (6.1.6.1) + activesupport (= 6.1.6.1) + activerecord (6.1.6.1) + activemodel (= 6.1.6.1) + activesupport (= 6.1.6.1) + activestorage (6.1.6.1) + actionpack (= 6.1.6.1) + activejob (= 6.1.6.1) + activerecord (= 6.1.6.1) + activesupport (= 6.1.6.1) + marcel (~> 1.0) + mini_mime (>= 1.1.0) + activesupport (6.1.6.1) concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - acts-as-taggable-on (5.0.0) - activerecord (>= 4.2.8) - addressable (2.7.0) + i18n (>= 1.6, < 2) + minitest (>= 5.1) + tzinfo (~> 2.0) + zeitwerk (~> 2.3) + addressable (2.8.0) public_suffix (>= 2.0.2, < 5.0) - airbrussh (1.4.0) + airbrussh (1.4.1) sshkit (>= 1.6.1, != 1.7.0) - apartment (2.2.1) - activerecord (>= 3.1.2, < 6.0) - parallel (>= 0.7.1) - public_suffix (>= 2) - rack (>= 1.3.6) - arel (9.0.0) - bcrypt (3.1.13) - bcrypt (3.1.13-java) + aws-eventstream (1.2.0) + aws-partitions (1.613.0) + aws-sdk-core (3.131.5) + aws-eventstream (~> 1, >= 1.0.2) + aws-partitions (~> 1, >= 1.525.0) + aws-sigv4 (~> 1.1) + jmespath (~> 1, >= 1.6.1) + aws-sdk-kms (1.58.0) + aws-sdk-core (~> 3, >= 3.127.0) + aws-sigv4 (~> 1.1) + aws-sdk-s3 (1.114.0) + aws-sdk-core (~> 3, >= 3.127.0) + aws-sdk-kms (~> 1) + aws-sigv4 (~> 1.4) + aws-sigv4 (1.5.1) + aws-eventstream (~> 1, >= 1.0.2) builder (3.2.4) - cancancan (2.3.0) - capistrano (3.12.1) + cancancan (3.4.0) + capistrano (3.17.0) airbrussh (>= 1.0.0) i18n rake (>= 10.0.0) sshkit (>= 1.9.0) - capistrano-bundler (1.6.0) + capistrano-bundler (2.1.0) capistrano (~> 3.1) - capistrano-passenger (0.2.0) + capistrano-passenger (0.2.1) capistrano (~> 3.0) - capistrano-rails (1.4.0) + capistrano-rails (1.6.2) capistrano (~> 3.1) - capistrano-bundler (~> 1.1) - capistrano-rbenv (2.1.6) + capistrano-bundler (>= 1.1, < 3) + capistrano-rbenv (2.2.0) capistrano (~> 3.1) sshkit (~> 1.3) - carrierwave (1.3.1) + carrierwave (1.3.2) activemodel (>= 4.0.0) activesupport (>= 4.0.0) mime-types (>= 1.16) - carrierwave-base64 (2.8.0) - carrierwave (>= 0.8.0) - mime-types (~> 3.0) - mimemagic (~> 0.3.2) + ssrf_filter (~> 1.0) case_transform (0.2) activesupport - concurrent-ruby (1.1.6) - coveralls (0.8.23) - json (>= 1.8, < 3) - simplecov (~> 0.16.1) - term-ansicolor (~> 1.3) - thor (>= 0.19.4, < 2.0) - tins (~> 1.6) - crack (0.4.3) - safe_yaml (~> 1.0.0) + cliver (0.3.2) + concurrent-ruby (1.1.10) + coveralls (0.7.1) + multi_json (~> 1.3) + rest-client + simplecov (>= 0.7) + term-ansicolor + thor + crack (0.4.5) + rexml crass (1.0.6) - database_cleaner (1.8.3) - diff-lcs (1.3) - docile (1.3.2) - erubi (1.9.0) - factory_bot (5.1.1) - activesupport (>= 4.2.0) - factory_bot_rails (5.1.1) - factory_bot (~> 5.1.0) - railties (>= 4.2.0) - ffi (1.12.2) - ffi (1.12.2-java) - ffi (1.12.2-x64-mingw32) - ffi (1.12.2-x86-mingw32) - globalid (0.4.2) - activesupport (>= 4.2.0) + database_cleaner (2.0.1) + database_cleaner-active_record (~> 2.0.0) + database_cleaner-active_record (2.0.1) + activerecord (>= 5.a) + database_cleaner-core (~> 2.0.0) + database_cleaner-core (2.0.1) + diff-lcs (1.5.0) + docile (1.4.0) + domain_name (0.5.20190701) + unf (>= 0.0.5, < 1.0.0) + erubi (1.10.0) + factory_bot (6.2.1) + activesupport (>= 5.0.0) + factory_bot_rails (6.2.0) + factory_bot (~> 6.2.0) + railties (>= 5.0.0) + faker (3.5.2) + i18n (>= 1.8.11, < 2) + faraday (1.10.0) + faraday-em_http (~> 1.0) + faraday-em_synchrony (~> 1.0) + faraday-excon (~> 1.1) + faraday-httpclient (~> 1.0) + faraday-multipart (~> 1.0) + faraday-net_http (~> 1.0) + faraday-net_http_persistent (~> 1.0) + faraday-patron (~> 1.0) + faraday-rack (~> 1.0) + faraday-retry (~> 1.0) + ruby2_keywords (>= 0.0.4) + faraday-em_http (1.0.0) + faraday-em_synchrony (1.0.0) + faraday-excon (1.1.0) + faraday-httpclient (1.0.1) + faraday-multipart (1.0.4) + multipart-post (~> 2) + faraday-net_http (1.0.1) + faraday-net_http_persistent (1.2.0) + faraday-patron (1.0.0) + faraday-rack (1.0.0) + faraday-retry (1.0.3) + ferrum (0.11) + addressable (~> 2.5) + cliver (~> 0.3) + concurrent-ruby (~> 1.1) + websocket-driver (>= 0.6, < 0.8) + ffi (1.15.5) + globalid (1.0.0) + activesupport (>= 5.0) hashdiff (1.0.1) - httparty (0.13.7) - json (~> 1.8) + http-accept (1.7.0) + http-cookie (1.0.5) + domain_name (~> 0.5) + httparty (0.20.0) + mime-types (~> 3.0) multi_xml (>= 0.5.2) httpclient (2.8.3) - i18n (1.8.2) + i18n (1.12.0) concurrent-ruby (~> 1.0) - json (1.8.6) - json (1.8.6-java) + image_processing (1.12.2) + mini_magick (>= 4.9.5, < 5) + ruby-vips (>= 2.0.17, < 3) + ipinfo-rails (1.0.1) + IPinfo (~> 1.0.1) + rack (~> 2.0) + jmespath (1.6.1) + json (2.6.2) jsonapi-renderer (0.2.2) - listen (3.1.5) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) - ruby_dep (~> 1.2) - loofah (2.4.0) + jwt (2.4.1) + kaminari (1.2.2) + activesupport (>= 4.1.0) + kaminari-actionview (= 1.2.2) + kaminari-activerecord (= 1.2.2) + kaminari-core (= 1.2.2) + kaminari-actionview (1.2.2) + actionview + kaminari-core (= 1.2.2) + kaminari-activerecord (1.2.2) + activerecord + kaminari-core (= 1.2.2) + kaminari-core (1.2.2) + listen (3.7.1) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + loofah (2.18.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) + lru_redux (1.1.0) mail (2.7.1) mini_mime (>= 0.1.1) - marcel (0.3.3) - mimemagic (~> 0.3.2) - method_source (0.9.2) - mime-types (3.3.1) + marcel (1.0.2) + method_source (1.0.0) + mime-types (3.4.1) mime-types-data (~> 3.2015) - mime-types-data (3.2019.1009) - mimemagic (0.3.4) - mini_magick (4.10.1) - mini_mime (1.0.2) - mini_portile2 (2.4.0) - minitest (5.14.0) + mime-types-data (3.2022.0105) + mini_magick (4.11.0) + mini_mime (1.1.2) + minitest (5.16.2) + multi_json (1.15.0) multi_xml (0.6.0) - multipart-post (2.1.1) - mysql2 (0.5.3) - mysql2 (0.5.3-x64-mingw32) - mysql2 (0.5.3-x86-mingw32) - mysql2 (0.5.3-x86-mswin32-60) - net-scp (2.0.0) - net-ssh (>= 2.6.5, < 6.0.0) - net-ssh (5.2.0) - nio4r (2.5.2) - nio4r (2.5.2-java) - nokogiri (1.10.9) - mini_portile2 (~> 2.4.0) - nokogiri (1.10.9-java) - nokogiri (1.10.9-x64-mingw32) - mini_portile2 (~> 2.4.0) - nokogiri (1.10.9-x86-mingw32) - mini_portile2 (~> 2.4.0) - oauth (0.5.4) - parallel (1.19.1) - pg (1.2.3) - pg (1.2.3-x64-mingw32) - pg (1.2.3-x86-mingw32) - public_suffix (4.0.3) - puma (4.3.3) + multipart-post (2.2.3) + mysql2 (0.5.4) + net-imap (0.3.1) + net-protocol + net-pop (0.1.2) + net-protocol + net-protocol (0.1.3) + timeout + net-scp (1.2.1) + net-ssh (>= 2.6.5) + net-smtp (0.3.2) + net-protocol + net-ssh (7.0.1) + netrc (0.11.0) + nio4r (2.5.8) + nokogiri (1.13.8-aarch64-linux) + racc (~> 1.4) + nokogiri (1.13.8-arm64-darwin) + racc (~> 1.4) + nokogiri (1.13.8-x86_64-darwin) + racc (~> 1.4) + nokogiri (1.13.8-x86_64-linux) + racc (~> 1.4) + oauth (0.5.10) + parallel (1.22.1) + pg (1.4.2) + public_suffix (4.0.7) + puma (4.3.12) nio4r (~> 2.0) - puma (4.3.3-java) - nio4r (~> 2.0) - rack (2.2.2) + racc (1.6.0) + rack (2.2.4) rack-cors (1.1.1) rack (>= 2.0.0) - rack-test (1.1.0) - rack (>= 1.0, < 3) - rails (5.2.4.1) - actioncable (= 5.2.4.1) - actionmailer (= 5.2.4.1) - actionpack (= 5.2.4.1) - actionview (= 5.2.4.1) - activejob (= 5.2.4.1) - activemodel (= 5.2.4.1) - activerecord (= 5.2.4.1) - activestorage (= 5.2.4.1) - activesupport (= 5.2.4.1) - bundler (>= 1.3.0) - railties (= 5.2.4.1) + rack-test (2.0.2) + rack (>= 1.3) + rails (6.1.6.1) + actioncable (= 6.1.6.1) + actionmailbox (= 6.1.6.1) + actionmailer (= 6.1.6.1) + actionpack (= 6.1.6.1) + actiontext (= 6.1.6.1) + actionview (= 6.1.6.1) + activejob (= 6.1.6.1) + activemodel (= 6.1.6.1) + activerecord (= 6.1.6.1) + activestorage (= 6.1.6.1) + activesupport (= 6.1.6.1) + bundler (>= 1.15.0) + railties (= 6.1.6.1) sprockets-rails (>= 2.0.0) rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) - rails-html-sanitizer (1.3.0) + rails-html-sanitizer (1.4.3) loofah (~> 2.3) - rails_api_auth (0.1.0) - bcrypt (~> 3.1.7) - httparty (~> 0.13.3) - rails (>= 3.2.6, < 6) - railties (5.2.4.1) - actionpack (= 5.2.4.1) - activesupport (= 5.2.4.1) + railties (6.1.6.1) + actionpack (= 6.1.6.1) + activesupport (= 6.1.6.1) method_source - rake (>= 0.8.7) - thor (>= 0.19.0, < 2.0) - rake (13.0.1) - rb-fsevent (0.10.3) + rake (>= 12.2) + thor (~> 1.0) + rake (13.0.6) + rb-fsevent (0.11.1) rb-inotify (0.10.1) ffi (~> 1.0) redis (3.3.5) - rspec-core (3.9.1) - rspec-support (~> 3.9.1) - rspec-expectations (3.9.1) + rest-client (2.1.0) + http-accept (>= 1.7.0, < 2.0) + http-cookie (>= 1.0.2, < 2.0) + mime-types (>= 1.16, < 4.0) + netrc (~> 0.8) + rexml (3.2.5) + rgeo (2.4.0) + ros-apartment (2.11.0) + activerecord (>= 5.0.0, < 7.1) + parallel (< 2.0) + public_suffix (>= 2.0.5, < 5.0) + rack (>= 1.3.6, < 3.0) + rspec-core (3.11.0) + rspec-support (~> 3.11.0) + rspec-expectations (3.11.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.9.0) - rspec-mocks (3.9.1) + rspec-support (~> 3.11.0) + rspec-mocks (3.11.1) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.9.0) - rspec-rails (3.9.1) - actionpack (>= 3.0) - activesupport (>= 3.0) - railties (>= 3.0) - rspec-core (~> 3.9.0) - rspec-expectations (~> 3.9.0) - rspec-mocks (~> 3.9.0) - rspec-support (~> 3.9.0) - rspec-support (3.9.2) - ruby_dep (1.5.0) - safe_yaml (1.0.5) - simplecov (0.16.1) + rspec-support (~> 3.11.0) + rspec-rails (5.1.2) + actionpack (>= 5.2) + activesupport (>= 5.2) + railties (>= 5.2) + rspec-core (~> 3.10) + rspec-expectations (~> 3.10) + rspec-mocks (~> 3.10) + rspec-support (~> 3.10) + rspec-support (3.11.0) + ruby-vips (2.1.4) + ffi (~> 1.12) + ruby2_keywords (0.0.5) + shoulda-matchers (4.5.1) + activesupport (>= 4.2.0) + simplecov (0.21.2) docile (~> 1.1) - json (>= 1.8, < 3) - simplecov-html (~> 0.10.0) - simplecov-html (0.10.2) - spring (2.1.0) + simplecov-html (~> 0.11) + simplecov_json_formatter (~> 0.1) + simplecov-html (0.12.3) + simplecov-lcov (0.8.0) + simplecov_json_formatter (0.1.4) + spring (2.1.1) spring-watcher-listen (2.0.1) listen (>= 2.7, < 4.0) spring (>= 1.2, < 3.0) - sprockets (4.0.0) + sprockets (4.1.1) concurrent-ruby (~> 1.0) rack (> 1, < 3) - sprockets-rails (3.2.1) - actionpack (>= 4.0) - activesupport (>= 4.0) + sprockets-rails (3.4.2) + actionpack (>= 5.2) + activesupport (>= 5.2) sprockets (>= 3.0.0) - sshkit (1.21.0) + sshkit (1.21.2) net-scp (>= 1.1.2) net-ssh (>= 2.8.0) + ssrf_filter (1.0.7) sync (0.5.0) term-ansicolor (1.7.1) tins (~> 1.0) - test-prof (0.11.3) - thor (1.0.1) - thread_safe (0.3.6) - thread_safe (0.3.6-java) - tins (1.24.1) + thor (1.2.1) + timeout (0.3.0) + tins (1.31.1) sync - tzinfo (1.2.6) - thread_safe (~> 0.1) - tzinfo-data (1.2020.1) - tzinfo (>= 1.0.0) + tzinfo (2.0.5) + concurrent-ruby (~> 1.0) + unf (0.1.4) + unf_ext + unf_ext (0.0.8.2) vimeo (1.5.4) httparty (>= 0.4.5) httpclient (>= 2.1.5.2) json (>= 1.1.9) multipart-post (>= 1.0.1) oauth (>= 0.4.3) - webmock (3.8.3) - addressable (>= 2.3.6) + webmock (3.14.0) + addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) - websocket-driver (0.7.1) - websocket-extensions (>= 0.1.0) - websocket-driver (0.7.1-java) + websocket-driver (0.7.5) websocket-extensions (>= 0.1.0) - websocket-extensions (0.1.4) + websocket-extensions (0.1.5) youtube_rails (1.2.2) - yt (0.32.6) + yt (0.33.4) activesupport + zeitwerk (2.6.0) PLATFORMS - java - ruby - x64-mingw32 - x86-mingw32 - x86-mswin32 + aarch64-linux + arm64-darwin-24 + x86_64-darwin-20 + x86_64-darwin-21 + x86_64-linux DEPENDENCIES actionview (>= 5.2.2.1) - active_model_serializers (~> 0.10.0.rc3) - acts-as-taggable-on (~> 5.0) - apartment - cancancan (~> 2.0) + active_model_serializers (~> 0.10.12) + aws-sdk-s3 (~> 1) capistrano-passenger capistrano-rails capistrano-rbenv (~> 2.0) carrierwave (~> 1.0) - carrierwave-base64 coveralls database_cleaner ecds_rails_auth_engine! factory_bot factory_bot_rails - faker! - listen (>= 3.0.5, < 3.2) + faker + ferrum + image_processing (~> 1.2) + ipinfo-rails + kaminari + listen mini_magick mysql2 + net-imap + net-pop + net-smtp pg puma (~> 4.3.0) rack (>= 2.0.6) rack-cors - rails (~> 5.2.0) + rails (~> 6.1.0) redis (~> 3.0) - rspec-rails (~> 3.5) - shoulda-matchers! + rgeo + ros-apartment + rspec-rails (~> 5.1.2) + shoulda-matchers (~> 4.5.1) + simplecov + simplecov-lcov spring spring-watcher-listen (~> 2.0.0) - test-prof + term-ansicolor tzinfo-data vimeo webmock @@ -350,4 +440,4 @@ DEPENDENCIES yt BUNDLED WITH - 2.1.4 + 2.2.22 diff --git a/Gemfile.original b/Gemfile.original new file mode 100644 index 00000000..e7d8b54d --- /dev/null +++ b/Gemfile.original @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' + +git_source(:github) do |repo_name| + repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?('/') + "https://github.com/#{repo_name}.git" +end + +# Bundle edge Rails instead: gem 'rails', github: 'rails/rails' +gem 'rails', '~> 5.2.0' +gem "rack", ">= 2.0.6" +gem 'pg' +gem 'mysql2' +# Multitenancy for Rails and ActiveRecord +gem 'apartment', '~> 2.2.0' +# For JSONAPI responses +gem 'active_model_serializers', '~> 0.10.0.rc3' +gem 'acts-as-taggable-on', '~> 5.0' +# Use Puma as the app server +gem 'puma', '~> 4.3.0' +# Use Redis adapter to run Action Cable in production +gem 'redis', '~> 3.0' +gem "actionview", ">= 5.2.2.1" +# Use ActiveModel has_secure_password +# gem 'bcrypt', '~> 3.1.7' + +# Social Auth +# gem 'ecds_rails_auth_engine', path: '../ecds_auth_engine' +gem 'ecds_rails_auth_engine', git: 'https://github.com/ecds/ecds_rails_auth_engine.git', :tag => 'v0.1.5' +gem 'cancancan', '~> 2.0' + +# Active Storage will land in 5.2 +gem 'carrierwave', '~> 1.0' +gem 'mini_magick' +gem 'carrierwave-base64' + +# Video provider APIs +gem 'vimeo' +gem 'yt' +gem 'youtube_rails' + +# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible +gem 'rack-cors' + +# Faker gem - use version compatible with Ruby 2.7+ +gem 'faker', '~> 2.20' + +group :development, :test do + # Call 'byebug' anywhere in the code to stop execution and get a debugger console + # gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] + gem "test-prof" +end + +group :development do + gem 'listen', '>= 3.0.5', '< 3.2' + # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring + gem 'spring' + gem 'spring-watcher-listen', '~> 2.0.0' + gem 'rspec-rails', '~> 3.5' + # Use Capistrano for deployment + gem 'capistrano-rails' + gem 'capistrano-rbenv', '~> 2.0' + gem 'capistrano-passenger' +end + +group :test do + gem "factory_bot", "~> 5.2" + gem 'factory_bot_rails', '~> 5.2' + gem 'shoulda-matchers', '~> 4.0' + gem 'database_cleaner' + gem 'coveralls', require: false + gem 'webmock' +end + +# Windows does not include zoneinfo files, so bundle the tzinfo-data gem +gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] +gem "mimemagic", ">= 0.3.5" \ No newline at end of file diff --git a/README.md b/README.md index b12d07b9..634afa9c 100644 --- a/README.md +++ b/README.md @@ -4,19 +4,59 @@ The OpenTourBuilder API server provides a multi-tenant REST API for geographic t ## Requirements -- Ruby 2.4.1+ -- 5.2.x -- PostgreSQL 9.6.9 - - Plugins* - - pgcrypto - - uuid-ossp - - plpgsql +- rbenv +- Ruby 3.0.2 -\* Database plugins are enabled during the install process +## Install PostgreSQL client + +~~~bash +sudo apt install postgresql-client +~~~ + +## Install GDAL + +~~~bash +sudo apt install libpq-dev gdal-bin libgdal-dev +~~~ + +## Install Headless Chrome + +This is mostly taken from [this blog post](https://geekflare.com/install-chromium-ubuntu-centos/) and works on Ubuntu 20.04 + +~~~bash +sudo apt update +sudo apt install -y libappindicator1 fonts-liberation +wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb +sudo dpkg -i google-chrome*.deb +~~~ + +I always get some errors about missing/mis-matched dependencies. The following will fix that. + +~~~bash +sudo apt install -f +~~~ + +And try to install it again. + +~~~bash +sudo dpkg -i google-chrome*.deb +~~~ + +Assuming all went well, verify the install be running: + +~~~bash +google-chrome-stable -version +~~~ + +You should see something like: + +~~~bash +Google Chrome 91.0.4472.106 +~~~ ## Build Status -[![Build Status](https://travis-ci.com/ecds/otb-api-server.svg?branch=develop)](https://travis-ci.com/ecds/otb-api-server) +TODO: Add CircleCI ## Installation @@ -70,6 +110,7 @@ cap deploy staging We use the [Git-Flow](https://danielkummer.github.io/git-flow-cheatsheet/) branching model. Please submit pull requests against the develop branch. ### Code of conduct + [Code of Conduct](CODE_OF_CONDUCT.md) ## License diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index f380099e..9a16d01c 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -5,4 +5,27 @@ class ApplicationController < ActionController::API include Response include ExceptionHandler include EcdsRailsAuthEngine::CurrentUser + if Rails.env == 'test' + include ActiveStorage::SetCurrent + end + + before_action :set_no_cache_control, only: [:index, :show] + + def set_no_cache_control + # Prevent the client from caching GET responses. + # If you, for example, look at a tour at https://battle-of-atlanta.opentour.site + # and your browser caches the API responses. then you go edit that tour + # at https://opentour.site/admin/battle-or-atlanta, your browser will use some of + # those previously cached responses. the problem is, those cached responses have a response header + # + # access-control-allow-origin: https://battle-of-atlanta.opentour.site + # + # But now your origin is https://opentour.site and the browser blocks the response + # and throws a cross origin error + response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate' + response.headers['Pragma'] = 'no-cache' + response.headers['Expires'] = '-1' + expires_now() + stale?(SecureRandom.hex(10)) + end end diff --git a/app/controllers/concerns/exception_handler.rb b/app/controllers/concerns/exception_handler.rb index 71fa04b5..7cd90aec 100644 --- a/app/controllers/concerns/exception_handler.rb +++ b/app/controllers/concerns/exception_handler.rb @@ -5,17 +5,17 @@ module ExceptionHandler # provides the more graceful `included` method extend ActiveSupport::Concern - included do - rescue_from ActiveRecord::RecordNotFound do |e| - json_response({ message: e.message }, :not_found) - end + # included do + # rescue_from ActiveRecord::RecordNotFound do |e| + # json_response({ message: e.message }, :not_found) + # end - rescue_from ActiveRecord::RecordInvalid do |e| - json_response({ message: e.message }, :unprocessable_entity) - end + # rescue_from ActiveRecord::RecordInvalid do |e| + # json_response({ message: e.message }, :unprocessable_entity) + # end - rescue_from ActionController::ParameterMissing do |e| - json_response({ message: e.message }, :unprocessable_entity) - end - end + # rescue_from ActionController::ParameterMissing do |e| + # json_response({ message: e.message }, :unprocessable_entity) + # end + # end end diff --git a/app/controllers/v1/tours_controller.rb b/app/controllers/v1/tours_controller.rb deleted file mode 100644 index 07fa72ee..00000000 --- a/app/controllers/v1/tours_controller.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -# app/controllers/v1/tours_controller.rb -# This is only for testing calls to versioned endpoints. -class V1::ToursController < ApplicationController - def index - json_response(message: Faker::Movies::HitchhikersGuideToTheGalaxy.quote) - end -end diff --git a/app/controllers/v3/authenticated_controller.rb b/app/controllers/v3/authenticated_controller.rb deleted file mode 100644 index 939c10d2..00000000 --- a/app/controllers/v3/authenticated_controller.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -# app/controllers/v3/authenticated_controller.rb -module V3 - class AuthenticatedController < V3Controller - # include RailsApiAuth::Authentication - - before_action :authenticate! - - def index - render json: { success: true } - end - end -end diff --git a/app/controllers/v3/flat_pages_controller.rb b/app/controllers/v3/flat_pages_controller.rb index 22d78d34..f1f736bc 100644 --- a/app/controllers/v3/flat_pages_controller.rb +++ b/app/controllers/v3/flat_pages_controller.rb @@ -1,59 +1,72 @@ # frozen_string_literal: true -class V3::FlatPagesController < V3Controller - before_action :set_flat_page, only: [:show, :update, :destroy] - authorize_resource +module V3 + class FlatPagesController < V3Controller + before_action :set_record, only: [:show, :update, :destroy] + #authorize_resource - # GET /v3/flat_pages - def index - @flat_pages = FlatPage.all - - render json: @flat_pages - end - - # GET /v3/flat_pages/1 - def show - render json: @flat_page - end + # GET /v3/records + def index + @records = if current_user.current_tenant_admin? + FlatPage.all + elsif current_user.tours.present? + current_user.tours.map { |tour| tour.flat_pages }.flatten.uniq + else + Tour.published.map { |tour| tour.flat_pages }.flatten.uniq + end + render json: @records + end - # POST /v3/flat_pages - def create - @flat_page = FlatPage.new(flat_page_params) + # POST /v3/records + def create + if @allowed + @record = FlatPage.new(record_params) - if @flat_page.save - render json: @flat_page, status: :created, location: "/#{Apartment::Tenant.current}/flat-pages/#{@flat_page.id}" - else - render json: @flat_page.errors, status: :unprocessable_entity + if @record.save + render json: @record, status: :created, location: "/#{Apartment::Tenant.current}/flat-pages/#{@record.id}" + else + render json: serialize_errors, status: :unprocessable_entity + end + else + head 401 + end end - end - # PATCH/PUT /v3/flat_pages/1 - def update - if @flat_page.update(flat_page_params) - render json: @flat_page - else - render json: @flat_page.errors, status: :unprocessable_entity + # PATCH/PUT /v3/records/1 + def update + if @allowed + if @record.update(record_params) + render json: @record + else + render json: serialize_errors, status: :unprocessable_entity + end + else + head 401 + end end - end - # DELETE /v3/flat_pages/1 - def destroy - @flat_page.destroy - end + # DELETE /v3/records/1 - private - # Use callbacks to share common setup or constraints between actions. - def set_flat_page - @flat_page = FlatPage.find(params[:id]) - end - # Only allow a trusted parameter "white list" through. - def flat_page_params - ActiveModelSerializers::Deserialization - .jsonapi_parse( - params, only: [ - :title, :content, :tours - ] - ) - end + private + # Use callbacks to share common setup or constraints between actions. + def set_record + _record = FlatPage.find(params[:id]) + @record = _record.published || @allowed ? _record : FlatPage.new(id: params[:id]) + end + + # Only allow a trusted parameter "white list" through. + def record_params + ActiveModelSerializers::Deserialization + .jsonapi_parse( + params, only: [ + :title, :body, :tours + ] + ) + end + + def allowed? + @allowed = current_user&.current_tenant_admin? || current_user.tours&.any? { |tour| Tour.all.include?(tour) } + end + end end diff --git a/app/controllers/v3/geojson_tours_controller.rb b/app/controllers/v3/geojson_tours_controller.rb index 63ad37bc..3a00ff4e 100644 --- a/app/controllers/v3/geojson_tours_controller.rb +++ b/app/controllers/v3/geojson_tours_controller.rb @@ -1,30 +1,69 @@ # frozen_string_literal: true # -# +# Endpoint that returns a tour serialized as GeoJSON # module V3 - class GeojsonToursController < V3Controller - skip_authorization_check + class GeojsonToursController < ApplicationController + include ActionView::Helpers::SanitizeHelper + def show @tour = Tour.find(params[:id]) - render json: { type: 'FeatureCollection', features: @tour.stops.map { |s| feature(s) } }.to_json + # if @tour.published + geojosn = { + type: 'FeatureCollection', + crs: { + type: 'name', + properties: { + name: 'urn:ogc:def:crs:EPSG::4326' + } + }, + meta: meta_content, + features: @tour.tour_stops.map { |tour_stop| feature(tour_stop.position, tour_stop.stop) } + } + render json: geojosn.to_json + # render json: { type: 'FeatureCollection', meta: meta_content, features: @tour.tour_stops.map { |tour_stop| feature(tour_stop.position, tour_stop.stop) } }.to_json + # else + # head 401 + # end end private - def feature(stop) + def meta_content + { + title: @tour.title, + description: sanitize(@tour.description), + images: @tour.media.map do |m| + { + caption: m.caption, + full: m.files[:desktop], + thumb: m.files[:mobile] + } + end + } + end + + def feature(position, stop) + stop.media.map { |m| m.caption = nil if m.caption.blank? } { type: 'Feature', geometry: { - type: 'Point', - coordinates: [stop.lng.to_f, stop.lat.to_f] - }, - properties: { - title: stop.title, - description: stop.description, - images: stop.media.map(&:desktop).map { |m| "#{request.protocol}#{request.host}/#{m}" } - } + type: 'Point', + coordinates: [stop.lng.to_f, stop.lat.to_f] + }, + properties: { + title: stop.title, + description: sanitize(stop.description), + position: position, + images: stop.media.map do |m| + { + caption: m.caption, + full: m.files[:desktop], + thumb: m.files[:mobile] + } + end + } } end end diff --git a/app/controllers/v3/map_icons_controller.rb b/app/controllers/v3/map_icons_controller.rb new file mode 100644 index 00000000..e1f1e72f --- /dev/null +++ b/app/controllers/v3/map_icons_controller.rb @@ -0,0 +1,38 @@ +module V3 + class MapIconsController < V3Controller + + def index + render json: MapIcon.all + end + + def create + if crud_allowed? + @record = MapIcon.new(record_params) + if @record.save + render json: @record, status: :created + else + render json: serialize_errors, status: :unprocessable_entity + end + else + head 401 + end + end + + private + + # Only allow a trusted parameter "white list" through. + def record_params + ActiveModelSerializers::Deserialization + .jsonapi_parse( + params, only: [ + :base_sixty_four, :filename, :stop + ] + ) + end + + def set_record + _record = MapIcon.find(params[:id]) + @record = _record&.published || @allowed ? _record : MapIcon.new(id: params[:id]) + end + end +end diff --git a/app/controllers/v3/map_overlays_controller.rb b/app/controllers/v3/map_overlays_controller.rb new file mode 100644 index 00000000..0f71ed97 --- /dev/null +++ b/app/controllers/v3/map_overlays_controller.rb @@ -0,0 +1,33 @@ +module V3 + class MapOverlaysController < V3Controller + def create + if crud_allowed? + @record = MapOverlay.new(record_params) + if @record.save + render json: @record, status: :created + else + render json: serialize_errors, status: :unprocessable_entity + end + else + head 401 + end + end + + private + + # Only allow a trusted parameter "white list" through. + def record_params + ActiveModelSerializers::Deserialization + .jsonapi_parse( + params, only: [ + :south, :east, :north, :west, :base_sixty_four, :filename, :tour, :stop + ] + ) + end + + def set_record + _record = MapOverlay.find(params[:id]) + @record = _record&.published || @allowed ? _record : MapOverlay.new(id: params[:id]) + end + end +end diff --git a/app/controllers/v3/media_controller.rb b/app/controllers/v3/media_controller.rb index 0b81bf0e..a6b09925 100644 --- a/app/controllers/v3/media_controller.rb +++ b/app/controllers/v3/media_controller.rb @@ -3,75 +3,86 @@ # app/controllers/v3/media_controller.rb module V3 class MediaController < V3Controller - before_action :set_medium, only: [:show, :update, :destroy] - authorize_resource + # include Pagy::Backend + before_action :set_record, only: [:show, :update, :destroy, :file] + # GET /media def index # TODO: This ins not ideal, we use these `not_in_*` scopes to make the list of media avaliable to add # to a stop or tour. But the paramerter does not make sense when just looking at it. Needs clearer language. - @media = if params[:stop_id] - Medium.not_in_stop(params[:stop_id]).or(Medium.no_stops) - elsif params[:tour_id] - Medium.not_in_tour(params[:tour_id]).or(Medium.no_tours) - else + @media = if (current_user && current_user.current_tenant_admin?) Medium.all + else + Medium.all.map { |medium| medium if medium.published }.compact + end + # pagy, @media = pagy(@media) + # pagy_headers_merge(pagy) + if params[:page].present? + @media = @media.page params[:page] + self.set_pagination_header end render json: @media end - + # GET /media/1 def show - if @medium.published || current_user.id.present? - render json: @medium + if @record.published || current_user.id.present? + render json: @record else - head 401 + render json: { data: { id: 0, type: 'media', attributes: { title: '....' } } } end end # POST /media def create - @medium = Medium.new(medium_params) + if allowed? + @record = Medium.new(record_params) - if @medium.save - render json: @medium, status: :created, location: "/#{Apartment::Tenant.current}/media/#{@medium.id}" + if @record.save + render json: @record, status: :created, location: "/#{Apartment::Tenant.current}/media/#{@record.id}" + else + render json: serialize_errors, status: :unprocessable_entity + end else - render json: @medium.errors, status: :unprocessable_entity + render json: {}, status: :unauthorized end end - # PATCH/PUT /media/1 - def update - if @medium.update(update_medium_params) - render json: @medium - else - render json: @medium.errors, status: :unprocessable_entity - end + # Use callbacks to share common setup or constraints between actions. + def set_record + @record = Medium.find(params[:id]) end - # DELETE /media/1 - def destroy - @medium.destroy + # Only allow a trusted parameter "white list" through. + def record_params + ActiveModelSerializers::Deserialization + .jsonapi_parse( + params, only: [ + :title, :caption, :original_image, :stops, :tours, :video, :stop_id, :tour_id, :base_sixty_four, :video_provider, :embed, :filename + ] + ) end - # private - # Use callbacks to share common setup or constraints between actions. - def set_medium - @medium = Medium.find(params[:id]) - end + private - # Only allow a trusted parameter "white list" through. - def medium_params - ActiveModelSerializers::Deserialization - .jsonapi_parse( - params, only: [ - :title, :caption, :original_image, :stops, :tours, :video, :stop_id, :tour_id - ] - ) - end + def set_pagination_header(name=:media, options = {}) + scope = instance_variable_get("@#{name}") + request_params = request.query_parameters + url_without_params = request.original_url.slice(0..(request.original_url.index("?")-1)) unless request_params.empty? + url_without_params ||= request.original_url - def update_medium_params - self.medium_params.except(:original_image) - end + page = {} + page[:first] = 1 if scope.total_pages > 1 && !scope.first_page? + page[:last] = scope.total_pages if scope.total_pages > 1 && !scope.last_page? + page[:next] = scope.current_page + 1 unless scope.last_page? + page[:prev] = scope.current_page - 1 unless scope.first_page? + pagination_links = [] + page.each do |k, v| + new_request_hash= request_params.merge({:page => v}) + pagination_links << "<#{url_without_params}?#{new_request_hash.to_param}>; rel=\"#{k}\"" + end + headers["Link"] = pagination_links.join(", ") + end end end diff --git a/app/controllers/v3/modes_controller.rb b/app/controllers/v3/modes_controller.rb index 3dcc7531..678e29de 100644 --- a/app/controllers/v3/modes_controller.rb +++ b/app/controllers/v3/modes_controller.rb @@ -2,15 +2,14 @@ # app/controllers/v3/modes_controller.rb module V3 - class ModesController < V3Controller - # before_action :set_mode, only: [:show, :update, :destroy] - authorize_resource - + class ModesController < ApplicationController # GET /modes def index - @modes = Mode.all + json_response Mode.all + end - render json: @modes + def show + json_response Mode.find(params[:id]) end end end diff --git a/app/controllers/v3/stop_media_controller.rb b/app/controllers/v3/stop_media_controller.rb index 0e808ce1..3f42d292 100644 --- a/app/controllers/v3/stop_media_controller.rb +++ b/app/controllers/v3/stop_media_controller.rb @@ -1,67 +1,36 @@ -class V3::StopMediaController < V3Controller - before_action :set_stop_medium, only: [:show, :update, :destroy] - authorize_resource - - # GET /v3/stop_media - def index - @stop_media = if params[:stop_id] && params[:medium_id] - StopMedium.where(stop_id: params[:stop_id]).where(medium_id: params[:medium_id]).first || {} - else - StopMedium.all +module V3 + class StopMediaController < V3::TourRelationsController + # GET /v3/stop_media + def index + # @stop_media = if params[:tour_id] && params[:medium_id] + # StopMedium.where(tour_id: params[:tour_id]).where(medium_id: params[:medium_id]).first || {} + # else + # StopMedium.all + # end + + @stop_media = StopMedium.all + + unless current_user&.current_tenant_admin? || current_user.tours.present? + @stop_media = @stop_media.reject { |stop_medium| !stop_medium.stop.published } + end + + render json: @stop_media end - render json: @stop_media - end - - # GET /v3/stop_media/1 - def show - render json: @stop_medium, - include: [ - # 'media' - ] - end - - # POST /v3/stop_media - def create - @stop_medium = StopMedium.new(stop_medium_params) - if @stop_medium.save - render json: @stop_medium, status: :created, location: "/#{Apartment::Tenant.current}/stop-medium/#{@stop_medium.id}" - else - render json: @stop_medium.errors, status: :unprocessable_entity - end - end - - # PATCH/PUT /v3/stop_media/1 - def update - if @stop_medium.update(stop_medium_params) - render json: @stop_medium - else - render json: @stop_medium.errors, status: :unprocessable_entity - end + private + # Only allow a trusted parameter "white list" through. + def record_params + ActiveModelSerializers::Deserialization + .jsonapi_parse( + params, only: [ + :medium, :stop, :position + ] + ) + end + + def set_record + _record = StopMedium.find(params[:id]) + @record = _record&.published || @allowed ? _record : StopMedium.new(id: params[:id]) + end end - - # DELETE /v3/stop_media/1 - def destroy - @stop_medium.destroy - end - - private - # Use callbacks to share common setup or constraints between actions. - def set_stop_medium - @stop_medium = StopMedium.find(params[:id]) - end - - # Only allow a trusted parameter "white list" through. - def stop_medium_params - ActiveModelSerializers::Deserialization - .jsonapi_parse( - params, only: [ - :medium, :stop, :position, :medium_id, :stop_id - ] - ) - end - - def set_stop_medium - @stop_medium = StopMedium.find_by!(id: params[:id]) - end end diff --git a/app/controllers/v3/stops_controller.rb b/app/controllers/v3/stops_controller.rb index 6b3b359c..6d7b94f7 100644 --- a/app/controllers/v3/stops_controller.rb +++ b/app/controllers/v3/stops_controller.rb @@ -1,88 +1,73 @@ # frozen_string_literal: true # /app/controllers/v3/stops_controller.rb -# module V3 -class V3::StopsController < V3Controller - # before_action :set_tour - before_action :set_stop, only: [:show, :update, :destroy] - authorize_resource - +module V3 + class StopsController < V3::TourRelationsController # GET /stops - def index - @stops = if params[:tour_id] - Stop.not_in_tour(params[:tour_id]).or(Stop.no_tours) - elsif params[:slug] - # stop = StopSlug.find_by(slug: params[:slug]).stop - stop = Stop.by_slug_and_tour(params[:slug], params[:tour_id]) - else - Stop.all + def index + @records = if current_user.current_tenant_admin? + Stop.all + elsif current_user.tours.present? + current_user.tours.map { |tour| tour.stops }.flatten.uniq + else + Tour.published.map { |tour| tour.stops }.flatten.uniq + end + render json: @records end - render json: @stops, - include: [ - 'media', - 'stop_media' - ] - end - - # GET /stops/1 - def show - render json: @stop, - include: [ - 'media', - 'stop_media' - ] - end - # POST /stops - def create - @stop = Stop.new(stop_params) - if @stop.save - render json: @stop, status: :created, location: "/#{Apartment::Tenant.current}/#{@stop.id}" - else - render json: @stop.errors, status: :unprocessable_entity + # POST /stops + def create + if crud_allowed? + @record = Stop.new(stop_params) + if @record.save + render json: @record, status: :created, location: "/#{Apartment::Tenant.current}/#{@record.id}" + end + else + head 401 + end end - end - # PATCH/PUT /stops/1 - def update - if @stop.update(stop_params) - render json: @stop, location: "/#{Apartment::Tenant.current}/stops/#{@stop.id}" - else - render json: @stop.errors, status: :unprocessable_entity + # PATCH/PUT /stops/1 + def update + if crud_allowed? + if @record&.update(stop_params) + render json: @record, location: "/#{Apartment::Tenant.current}/stops/#{@record.id}" + end + else + head 401 + end end - end - - # DELETE /stops/1 - def destroy - @stop.destroy - end - - private - # Only allow a trusted parameter "white list" through. - def stop_params - ActiveModelSerializers::Deserialization - .jsonapi_parse( - params, only: [ - :title, :description, :lat, :lng, - :parking_lat, :parking_lng, :media, - :address, :tours, :direction_notes, - :meta_description - ] - ) + def destroy + if !crud_allowed? + head 401 + elsif crud_allowed? && @record.orphaned + @record.destroy + elsif crud_allowed? && !@record.orphaned + head 405 + end end - # Use callbacks to share common setup or constraints between actions. + private - def set_tour - @tour = Tour.find(params[:tour_id]) - end + # Only allow a trusted parameter "white list" through. + def stop_params + ActiveModelSerializers::Deserialization + .jsonapi_parse( + params, only: [ + :title, :description, :lat, :lng, + :parking_lat, :parking_lng, :media, + :address, :tours, :direction_notes, + :meta_description, :parking_address, + :icon_color, :map_icon + ] + ) + end - def set_stop - @stop = Stop.find(params[:id]) - end - - def set_tour_stop - @stop = @tour.stops.find_by!(id: params[:id]) if @tour - end + # Callbacks + def set_record + _record = Stop.find_by(id: params[:id]) + @record = _record&.published || @allowed ? _record : Stop.new(id: params[:id]) + end + end end diff --git a/app/controllers/v3/themes_controller.rb b/app/controllers/v3/themes_controller.rb index 33833f6a..c2739267 100644 --- a/app/controllers/v3/themes_controller.rb +++ b/app/controllers/v3/themes_controller.rb @@ -3,55 +3,35 @@ # app/controllers/v3/themes_controller.rb module V3 class ThemesController < V3Controller - before_action :set_theme, only: [:show, :update, :destroy] - authorize_resource - # GET /themes def index - @themes = Theme.all - - render json: @themes + render json: Theme.all end # GET /themes/1 def show - render json: @theme + render json: @record end # POST /themes def create - @theme = Theme.new(theme_params) - - if @theme.save - render json: @theme, status: :created, location: @theme - else - render json: @theme.errors, status: :unprocessable_entity - end + head 405 end # PATCH/PUT /themes/1 def update - if @theme.update(theme_params) - render json: @theme - else - render json: @theme.errors, status: :unprocessable_entity - end + head 405 end # DELETE /themes/1 def destroy - @theme.destroy + head 405 end private - # Use callbacks to share common setup or constraints between actions. - def set_theme - @theme = Theme.find(params[:id]) - end - # Only allow a trusted parameter "white list" through. - def theme_params - params.fetch(:theme, {}) + def set_record + @record = Theme.find(params[:id]) end -end + end end diff --git a/app/controllers/v3/tour_authors_controller.rb b/app/controllers/v3/tour_authors_controller.rb new file mode 100644 index 00000000..bd8e4b73 --- /dev/null +++ b/app/controllers/v3/tour_authors_controller.rb @@ -0,0 +1,44 @@ +module V3 + class TourAuthorsController < ApplicationController + before_action :set_tour_author, only: [:show] + + # GET /tour_authors + def index + if current_user&.current_tenant_admin? + render json: TourAuthor.all + else + head 401 + end + end + + # GET /tour_authors/1 + def show + if current_user&.current_tenant_admin? + render json: @tour_author + else + head 401 + end + end + + # POST /tour_authors + def create + head 405 + end + + # PATCH/PUT /tour_set_admins/1 + def update + head 405 + end + + # DELETE /tour_set_admins/1 + def destroy + head 405 + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_tour_author + @tour_author = TourAuthor.find(params[:id]) + end + end +end diff --git a/app/controllers/v3/tour_collections_controller.rb b/app/controllers/v3/tour_collections_controller.rb deleted file mode 100644 index 8fbbd9ca..00000000 --- a/app/controllers/v3/tour_collections_controller.rb +++ /dev/null @@ -1,54 +0,0 @@ -# frozen_string_literal: true - - class V3::TourCollectionsController < V3Controller - before_action :set_tour_collection, only: [:show, :update, :destroy] - authorize_resource - - # GET /v3/tour_collections - def index - @tour_collections = TourCollection.all - - render json: @tour_collections - end - - # GET /v3/tour_collections/1 - def show - render json: @tour_collection - end - - # POST /v3/tour_collections - def create - @tour_collection = TourCollection.new(tour_collection_params) - - if @tour_collection.save - render json: @tour_collection, status: :created, location: @tour_collection - else - render json: @tour_collection.errors, status: :unprocessable_entity - end - end - - # PATCH/PUT /v3/tour_collections/1 - def update - if @tour_collection.update(tour_collection_params) - render json: @tour_collection - else - render json: @tour_collection.errors, status: :unprocessable_entity - end - end - - # DELETE /v3/tour_collections/1 - def destroy - @tour_collection.destroy - end - - private - # Use callbacks to share common setup or constraints between actions. - def set_tour_collection - @tour_collection = TourCollection.find(params[:id]) - end - - # Only allow a trusted parameter "white list" through. - def tour_collection_params - params.fetch(:tour_collection, {}) - end - end diff --git a/app/controllers/v3/tour_flat_pages_controller.rb b/app/controllers/v3/tour_flat_pages_controller.rb index 003be43e..4126f7c9 100644 --- a/app/controllers/v3/tour_flat_pages_controller.rb +++ b/app/controllers/v3/tour_flat_pages_controller.rb @@ -1,59 +1,74 @@ # frozen_string_literal: true -class V3::TourFlatPagesController < V3Controller - before_action :set_tour_flat_page, only: [:show, :update, :destroy] - authorize_resource +# /app/controllers/v3/tour_stops_controller.rb +module V3 + class TourFlatPagesController < V3Controller + # GET /stops + def index + @tour_flat_pages = TourFlatPage.all - # GET /v3/tour_flat_pages - def index - @tour_flat_pages = TourFlatPage.all + unless current_user&.current_tenant_admin? || current_user.tours.present? + @tour_flat_pages = @tour_flat_pages.reject { |tour_flat_page| !tour_flat_page.tour.published } + end - render json: @tour_flat_pages - end + render json: @tour_flat_pages + end - # GET /v3/tour_flat_pages/1 - def show - render json: @tour_flat_page - end + # GET /stops/1 + def show + if @record&.tour.published || allowed? + render json: @record + else + render json: { data: {} } + end + # render json: { data: {} } if @record.nil? + # render json: @record, include: ['stop'] + end - # POST /v3/tour_flat_pages - def create - @tour_flat_page = TourFlatPage.new(tour_flat_page_params) + # POST /stops + def create + # Not created via the API + head 405 + end - if @tour_flat_page.save - render json: @tour_flat_page, status: :created, location: "/#{Apartment::Tenant.current}/flat-pages/#{@tour_flat_page.id}" - else - render json: @tour_flat_page.errors, status: :unprocessable_entity + # PATCH/PUT /stops/1 + def update + if @allowed + if @record.update(tour_stop_params) + render json: @record, location: "/#{Apartment::Tenant.current}/tour_stops/#{@record.id}" + else + render json: serialize_errors, status: :unprocessable_entity + end + else + head 401 + end end - end - # PATCH/PUT /v3/tour_flat_pages/1 - def update - if @tour_flat_page.update(tour_flat_page_params) - render json: @tour_flat_page - else - render json: @tour_flat_page.errors, status: :unprocessable_entity + # DELETE /stops/1 + def destroy + # Not deleted via the API + head 405 end - end - # DELETE /v3/tour_flat_pages/1 - def destroy - @tour_flat_page.destroy - end + private - private - # Use callbacks to share common setup or constraints between actions. - def set_tour_flat_page - @tour_flat_page = TourFlatPage.find(params[:id]) - end + # Only allow a trusted parameter "white list" through. + def tour_stop_params + ActiveModelSerializers::Deserialization + .jsonapi_parse( + params, only: [ + :stop, :tour, :position + ] + ) + end - # Only allow a trusted parameter "white list" through. - def tour_flat_page_params - ActiveModelSerializers::Deserialization - .jsonapi_parse( - params, only: [ - :tour, :flat_page, :position - ] - ) - end + def set_record + @record = TourFlatPage.find(params[:id]) + end + + def allowed? + @allowed = current_user&.current_tenant_admin? || current_user.tours&.any? { |tour| Tour.all.include?(tour) } + return @allowed + end + end end diff --git a/app/controllers/v3/tour_media_controller.rb b/app/controllers/v3/tour_media_controller.rb index caec3225..730e12b9 100644 --- a/app/controllers/v3/tour_media_controller.rb +++ b/app/controllers/v3/tour_media_controller.rb @@ -1,65 +1,36 @@ -class V3::TourMediaController < V3Controller - before_action :set_tour_medium, only: [:show, :update, :destroy] - authorize_resource - - # GET /v3/tour_media - def index - @tour_media = if params[:tour_id] && params[:medium_id] - TourMedium.where(tour_id: params[:tour_id]).where(medium_id: params[:medium_id]).first || {} - else - TourMedium.all - end - - render json: @tour_media - end - - # GET /v3/tour_media/1 - def show - render json: @tour_medium - end - - # POST /v3/tour_media - def create - @tour_medium = TourMedium.new(tour_medium_params) - - if @tour_medium.save - render json: @tour_medium, status: :created, location: @tour_medium - else - render json: @tour_medium.errors, status: :unprocessable_entity - end - end - - # PATCH/PUT /v3/tour_media/1 - def update - if @tour_medium.update(tour_medium_params) - render json: @tour_medium - else - render json: @tour_medium.errors, status: :unprocessable_entity +module V3 + class TourMediaController < V3::TourRelationsController + # GET /v3/tour_media + def index + # @tour_media = if params[:tour_id] && params[:medium_id] + # TourMedium.where(tour_id: params[:tour_id]).where(medium_id: params[:medium_id]).first || {} + # else + # TourMedium.all + # end + + @tour_media = TourMedium.all + + unless current_user&.current_tenant_admin? || current_user.tours.present? + @tour_media = @tour_media.reject { |tour_medium| !tour_medium.tour.published } + end + + render json: @tour_media end - end - # DELETE /v3/tour_media/1 - def destroy - @tour_medium.destroy + private + # Only allow a trusted parameter "white list" through. + def record_params + ActiveModelSerializers::Deserialization + .jsonapi_parse( + params, only: [ + :medium, :tour, :position + ] + ) + end + + def set_record + _record = TourMedium.find(params[:id]) + @record = _record&.published || @allowed ? _record : TourMedium.new(id: params[:id]) + end end - - private - # Use callbacks to share common setup or constraints between actions. - def set_tour_medium - @tour_medium = TourMedium.find(params[:id]) - end - - # Only allow a trusted parameter "white list" through. - def tour_medium_params - ActiveModelSerializers::Deserialization - .jsonapi_parse( - params, only: [ - :medium, :tour, :position - ] - ) - end - - def set_tour_medium - @tour_medium = TourMedium.find_by!(id: params[:id]) - end end diff --git a/app/controllers/v3/tour_modes_controller.rb b/app/controllers/v3/tour_modes_controller.rb deleted file mode 100644 index e1e7f7f5..00000000 --- a/app/controllers/v3/tour_modes_controller.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -# app/controllers/v3/tour_modes_controller.rb -module V3 - class TourModesController < V3Controller - authorize_resource - - # GET /tour_sets - def index - @tour_modes = TourMode.all - - render json: @tour_modes - end -end -end diff --git a/app/controllers/v3/tour_relations_controller.rb b/app/controllers/v3/tour_relations_controller.rb new file mode 100644 index 00000000..a4a94b86 --- /dev/null +++ b/app/controllers/v3/tour_relations_controller.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +# /app/controllers/v3/tour_relations_controller.rb +module V3 + class TourRelationsController < V3Controller + + def destroy + head 405 + end + + def allowed? + set_record if @record.nil? && params[:id].present? + @allowed = @record&.published || crud_allowed? + end + + def crud_allowed? + current_user&.current_tenant_admin? || + current_user.tours&.any? { |tour| Tour.all.include?(tour) } + end + end +end diff --git a/app/controllers/v3/tour_set_admins_controller.rb b/app/controllers/v3/tour_set_admins_controller.rb index 14d05fe7..33f9b2a1 100644 --- a/app/controllers/v3/tour_set_admins_controller.rb +++ b/app/controllers/v3/tour_set_admins_controller.rb @@ -2,55 +2,55 @@ module V3 class TourSetAdminsController < V3Controller - before_action :set_tour_set_admin, only: [:show, :update, :destroy] - authorize_resource - # GET /tour_set_admins def index - @tour_set_admins = TourSetAdmin.all - - render json: @tour_set_admins + if current_user&.super || current_user&.current_tenant_admin? + render json: TourSetAdmin.all + else + head 401 + end end # GET /tour_set_admins/1 def show - render json: @tour_set_admin + head 405 + # if current_user&.super || current_user&.current_tenant_admin? + # render json: @record + # else + # head 401 + # end end # POST /tour_set_admins def create - @tour_set_admin = TourSetAdmin.new(tour_set_admin_params) - - if @tour_set_admin.save - render json: @tour_set_admin, status: :created, location: @tour_set_admin - else - render json: @tour_set_admin.errors, status: :unprocessable_entity - end + head 405 + # @record = TourSetAdmin.new(tour_set_admin_params) + + # if @record.save + # render json: @record, status: :created, location: @record + # else + # render json: serialize_errors, status: :unprocessable_entity + # end end # PATCH/PUT /tour_set_admins/1 def update - if @tour_set_admin.update(tour_set_admin_params) - render json: @tour_set_admin - else - render json: @tour_set_admin.errors, status: :unprocessable_entity - end + head 405 end # DELETE /tour_set_admins/1 def destroy - @tour_set_admin.destroy + head 405 end private - # Use callbacks to share common setup or constraints between actions. - def set_tour_set_admin - @tour_set_admin = TourSetAdmin.find(params[:id]) - end - # Only allow a trusted parameter "white list" through. - def tour_set_admin_params - params.fetch(:tour_set_admin, {}) + # def tour_set_admin_params + # params.fetch(:tour_set_admin, {}) + # end + + def set_record + @record = TourSetAdmin.find(params[:id]) end end end diff --git a/app/controllers/v3/tour_sets_controller.rb b/app/controllers/v3/tour_sets_controller.rb index cfbbb149..600c7f65 100644 --- a/app/controllers/v3/tour_sets_controller.rb +++ b/app/controllers/v3/tour_sets_controller.rb @@ -4,76 +4,100 @@ # app/controllers/v3/tour_sets.rb module V3 class TourSetsController < V3Controller - before_action :set_tour_set, only: [:show, :update, :destroy] - authorize_resource - # GET /tour_sets def index - @tour_sets = [] + @records = [] if params[:subdir] && params[:subdir] != 'public' - @tour_sets = TourSet.find_by(subdir: params[:subdir]) - elsif current_user.super - @tour_sets = TourSet.all - elsif current_user.id.present? - @tour_sets = current_user.tour_sets + @records = TourSet.where(subdir: params[:subdir]) + if !@records.first&.published_tours&.empty? || current_user&.tour_sets.include?(@records.first) || current_user&.super + render json: @records + else + render json: TourSet.none + end + return + elsif current_user&.tour_sets.present? && !current_user.super + @records = published.concat(current_user.tour_sets).uniq else - #TourSet.all.reject {|ts| p ts.tours.empty?} - @tour_sets = TourSet.all.reject { |ts| ts.published_tours.empty? } + @records = TourSet.all end - - if current_user.current_tenant_admin? || current_user.super - render json: @tour_sets, include: [ 'admins' ] + + if current_user.tour_sets.present? || current_user.super + render json: @records, include: [ 'admins' ] else - render json: @tour_sets + if current_user&.tour_sets.empty? + @records = published + end + render json: @records end end # GET /tour_sets/1 def show - render json: @tour_set + if @allowed + render json: @record + else + render json: { data: { id: 0, type: 'tour_sets', attributes: { name: '....' } } } + end end # POST /tour_sets def create - @tour_set = TourSet.new(tour_set_params) + if crud_allowed? + @record = TourSet.new(record_params) - if @tour_set.save - # render json: @tour_set, status: :created, location: @tour_set - response = render json: @tour_set, status: :created, location: "/#{Apartment::Tenant.current}/#{@tour_set.id}" - return response + if @record.save + render json: @record, status: :created, location: "/#{Apartment::Tenant.current}/#{@record.id}" + else + render json: serialize_errors, status: :unprocessable_entity + end else - render json: @tour_set.errors, status: :unprocessable_entity + head 401 end end # PATCH/PUT /tour_sets/1 def update - if @tour_set.update(tour_set_params) - # render json: @tour_set - head :no_content + if crud_allowed? + if @record.update(record_params) + render json: @record + else + render json: serialize_errors, status: :unprocessable_entity + end else - render json: @tour_set.errors, status: :unprocessable_entity + head 401 end end - # DELETE /tour_sets/1 - def destroy - @tour_set.destroy - end - private # Use callbacks to share common setup or constraints between actions. - def set_tour_set - @tour_set = TourSet.find(params[:id]) + def set_record + @record = TourSet.find(params[:id]) + end + + def allowed? + set_record if @record.nil? && params[:id].present? + @allowed = if @record.nil? + crud_allowed? + else + current_user&.current_tenant_admin? || @record.published_tours.present? || current_user.tour_sets.include?(@record) + end + end + + def crud_allowed? + current_user&.super || (current_user.present? && @record&.in?(current_user.tour_sets)) + end + + def published + TourSet.all.reject { |tour_set| tour_set.published_tours.empty? } end # Only allow a trusted parameter "white list" through. - def tour_set_params + def record_params ActiveModelSerializers::Deserialization .jsonapi_parse( params, only: [ - :name, :tours, :admins + :name, :tours, :admins, :base_sixty_four, :logo_title ] ) end diff --git a/app/controllers/v3/tour_stops_controller.rb b/app/controllers/v3/tour_stops_controller.rb index 15e2a491..ecad029f 100644 --- a/app/controllers/v3/tour_stops_controller.rb +++ b/app/controllers/v3/tour_stops_controller.rb @@ -1,68 +1,86 @@ # frozen_string_literal: true # /app/controllers/v3/tour_stops_controller.rb -class V3::TourStopsController < V3Controller - before_action :set_tour_stop, only: [:show, :update, :destroy] - authorize_resource +module V3 + class TourStopsController < V3Controller + # GET /stops + def index + @records = if params[:fastboot] == 'true' + nil + elsif params[:tour] && params[:slug] + tour = Tour.find(params[:tour]) + if tour.published || allowed? + stop = Stop.by_slug_and_tour(params[:slug], params[:tour]).first + TourStop.find_by(tour: Tour.find(params[:tour]), stop: stop) + else + {} + end + elsif current_user.current_tenant_admin? + TourStop.all + else + Tour.published.map { |tour| tour.tour_stops }.flatten.uniq + end + if @records.nil? + render json: { data: { type: 'tour_stops', id: 0 } } + else + render json: @records, include: ['stop'] + end + end - # GET /stops - def index - @tour_stops = if params[:tour_id] && params[:stop_id] - TourStop.where(tour: Tour.find(params[:tour_id])).where(stop: Stop.find(params[:stop_id])).first || {} - elsif params[:tour] && params[:slug] - # stop = StopSlug.find_by(slug: params[:slug]) - stop = Stop.by_slug_and_tour(params[:slug], params[:tour]).first - # TourStop.where(tour: Tour.find(params[:tour])).where(stop: stop).first - TourStop.find_by(tour: Tour.find(params[:tour]), stop: stop) - else - TourStop.all + # GET /stops/1 + def show + if @record&.tour.published || allowed? + render json: @record + else + render json: { data: {} } + end + # render json: { data: {} } if @record.nil? + # render json: @record, include: ['stop'] end - render json: @tour_stops, include: ['stop'] - end - # GET /stops/1 - def show - render json: @tour_stop, include: ['stop'] - end + # POST /stops + def create + # Not created via the API + head 405 + end - # POST /stops - def create - @tour_stop = TourStop.new(tour_stop_params) - if @tour_stop.save - render json: @tour_stop, status: :created, location: @tour_stop - else - render json: @tour_stop.errors, status: :unprocessable_entity + # PATCH/PUT /stops/1 + def update + if @allowed + if @record.update(tour_stop_params) + render json: @record, location: "/#{Apartment::Tenant.current}/tour_stops/#{@record.id}" + else + render json: serialize_errors, status: :unprocessable_entity + end + else + head 401 + end end - end - # PATCH/PUT /stops/1 - def update - if @tour_stop.update(tour_stop_params) - # render json: @stop - head :no_content - else - render json: @tour_stop.errors, status: :unprocessable_entity + # DELETE /stops/1 + def destroy + head 405 end - end - # DELETE /stops/1 - def destroy - @tour_stop.destroy - end + private - private + # Only allow a trusted parameter "white list" through. + def tour_stop_params + ActiveModelSerializers::Deserialization + .jsonapi_parse( + params, only: [ + :stop, :tour, :position + ] + ) + end - # Only allow a trusted parameter "white list" through. - def tour_stop_params - ActiveModelSerializers::Deserialization - .jsonapi_parse( - params, only: [ - :stop, :tour, :position - ] - ) - end + def set_record + @record = TourStop.find(params[:id]) + end - def set_tour_stop - @tour_stop = TourStop.find_by!(id: params[:id]) - end + def allowed? + @allowed = current_user&.current_tenant_admin? || current_user.tours&.any? { |tour| Tour.all.include?(tour) } + return @allowed + end + end end diff --git a/app/controllers/v3/tours_controller.rb b/app/controllers/v3/tours_controller.rb index 075094f8..12abeec4 100644 --- a/app/controllers/v3/tours_controller.rb +++ b/app/controllers/v3/tours_controller.rb @@ -1,111 +1,102 @@ # frozen_string_literal: true # app/controllers/v3/tours_controller.rb -# module V3 -class V3::ToursController < V3Controller - before_action :set_tour, only: [:show, :update, :destroy] - # authorize_resource - load_and_authorize_resource - - # GET /tours - def index - @tours = if (params[:slug]) - tour = Slug.find_by(slug: params[:slug]).tour - if tour.published || current_user.current_tenant_admin? - tour +module V3 + class ToursController < V3Controller + # GET /tours + def index + @records = if (params[:slug]) + @record = Slug.find_by(slug: params[:slug]).tour + if @record.published || crud_allowed? + @record + else + nil + end + elsif (current_user && current_user.current_tenant_admin?) + Tour.all + elsif (current_user && current_user.id) + (current_user.tours + Tour.published).uniq else - nil + Tour.published + end + if @records.nil? + render json: { data: { id: 0, type: 'tours', attributes: { title: '....' } } } + else + render json: @records, each_serializer: V3::TourBaseSerializer end - elsif (current_user.current_tenant_admin?) - Tour.all - elsif (current_user.id) - current_user.tours - else - Tour.published - end - - if @tours.nil? - render json: {error: "not-found"}.to_json, status: 404 - else - render json: @tours, - include: [ - 'tour_stops', - 'stops', - 'stops.media', - 'stops.stop_media', - 'mode', - 'modes', - 'theme', - 'media', - 'tour_media', - 'flat_pages', - 'tour_flat_pages' - ] end - end + # GET /tours/1 + def show + request_loc = if request.env['ipinfo'].respond_to?('longitude') + { centerLng: request.env['ipinfo'].longitude, centerLat: request.env['ipinfo'].latitude } + else + { centerLng: -84.38979, centerLat: 33.75432 } + end - # GET /tours/1 - def show - render json: @tour, - include: [ - 'tour_stops', - 'stops', - 'stops.media', - 'stops.stop_media', - 'mode', - 'modes', - 'theme', - 'media', - 'tour_media', - 'flat_pages', - 'tour_flat_pages' - ] - end + if @record&.published || crud_allowed? + render json: @record, loc: request_loc + else + render json: { data: { id: 0, type: 'tours', attributes: { title: '....' } } } + end + end - # POST /tours - def create - @tour = Tour.new(tour_params) - if @tour.save - response = render json: @tour, status: :created, location: "/#{Apartment::Tenant.current}/tours/#{@tour.id}" - return response - else - render json: @tour.errors, status: :unprocessable_entity + # POST /tours + def create + if crud_allowed? + @record = Tour.new(tour_params) + if @record.save + render json: @record, status: :created, location: "/#{Apartment::Tenant.current}/tours/#{@record.id}" + else + render json: serialize_errors, status: :unprocessable_entity + end + else + head 401 + end end - end - # PATCH/PUT /tours/1 - def update - if @tour.update(tour_params) - render json: @tour, location: "/#{Apartment::Tenant.current}/tours/#{@tour.id}" - else - render json: @tour.errors, status: :unprocessable_entity + # PATCH/PUT /tours/1 + def update + if crud_allowed? + if @record.update(tour_params) + render json: @record, location: "/#{Apartment::Tenant.current}/tours/#{@record.id}" + else + render json: serialize_errors, status: :unprocessable_entity + end + else + head 401 + end end - end - # DELETE /tours/1 - def destroy - @tour.destroy - end + private + # Only allow a trusted parameter "white list" through. + def tour_params + ActiveModelSerializers::Deserialization + .jsonapi_parse( + params, only: [ + :title, :description, + :is_geo, :modes, :published, :theme_id, + :mode, :meta_description, :stops, + :media, :users, :flat_pages, :map_type, + :theme, :use_directions, :default_lng, + :link_address, :link_text, :restrict_bounds, + :restrict_bounds_to_overlay, :blank_map + ] + ) + end - private + def set_record + _record = Tour.find(params[:id]) + @record = _record&.published || @allowed ? _record : Tour.new(id: params[:id]) + end - # Use callbacks to share common setup or constraints between actions. - def set_tour - @tour = Tour.includes(:tour_stops).find(params[:id]) - end + # def allowed? + # @allowed = crud_allowed? || + # end - # Only allow a trusted parameter "white list" through. - def tour_params - ActiveModelSerializers::Deserialization - .jsonapi_parse( - params, only: [ - :title, :description, - :is_geo, :modes, :published, :theme_id, - :mode, :meta_description, :stops, - :media, :authors, :flat_pages, :map_type, - :theme - ] - ) - end -end \ No newline at end of file + def crud_allowed? + set_record if @record.nil? && params[:id].present? + current_user&.current_tenant_admin? || current_user.tours.include?(@record) + end + end +end diff --git a/app/controllers/v3/users_controller.rb b/app/controllers/v3/users_controller.rb index dae270c8..1a7bbd3c 100644 --- a/app/controllers/v3/users_controller.rb +++ b/app/controllers/v3/users_controller.rb @@ -1,98 +1,88 @@ # frozen_string_literal: true # app/controllers/v3/users_controller.rb +# +# Endpoints for User Model +# module V3 class UsersController < V3Controller - before_action :set_user, only: [:show, :update, :destroy] before_action :authenticate!, only: :me - authorize_resource # GET /users - # def index - # @users = User.all - - # render json: @users - # end def index - if current_user.present? && params['me'] - render json: current_user, include: ['tours', 'tour_sets', 'login'] - elsif current_user.current_tenant_admin? - render json: User.all - else - render json: { message: 'You are not autorized to to view this resource.' }.to_json, status: 401 + if current_user.present? + if params['me'] + render json: current_user + elsif current_user.current_tenant_admin? + render json: User.all + else + render json: { data: [] } + end end end # GET /users/1 def show - if current_user == @user || current_user.super - render json: @user + if current_user == @record || current_user.super + render json: @record, include_tours: true else render json: { message: 'You are not autorized to to view this resource.' }.to_json, status: 401 end end + # TODO: Is this endpoint ever used? # POST /users def create - @user = User.new(user_params) + if current_user&.super + @record = User.new(user_params) - if @user.save && user.create_login(login_params) - render json: @user, status: :created, location: @user + if @record.save + render json: @record, status: :created, location: "/#{Apartment::Tenant.current}/users/#{@record.id}" + else + render json: serialize_errors, status: :unprocessable_entity + end else - render json: @user.errors, status: :unprocessable_entity + head 401 end end # PATCH/PUT /users/1 def update - if @user.update(user_params) - render json: @user + if current_user&.super || current_user == @record + if @record.update(user_params) + render json: @record + else + render json: serialize_errors, status: :unprocessable_entity + end else - render json: @user.errors, status: :unprocessable_entity + head 401 end end # DELETE /users/1 def destroy - @user.destroy - end - - def me - user = @current_login.user - if user.nil? - render json: 'Invalid api token', status: :foo + if current_user&.super + @record.destroy else - render json: user + head 401 end end private - - # Use callbacks to share common setup or constraints between actions. - def set_user - @user = User.find(params[:id]) - end - # Only allow a trusted parameter "white list" through. def user_params ActiveModelSerializers::Deserialization .jsonapi_parse( params, only: [ - :displayname, :identification, :password, + :display_name, :identification, :password, :password_confirmation, :uid, :tour_sets, - :tours, :super + :tours, :super, :email, :terms_accepted ] ) end - def login_params - ActiveModelSerializers::Deserialization - .jsonapi_parse( - params, only: [ - :identification, :password, - :password_confirmation, :uid - ] - ) + def set_record + @record = User.find(params[:id]) end end end diff --git a/app/controllers/v3_controller.rb b/app/controllers/v3_controller.rb index 10c4bd48..d87c08df 100644 --- a/app/controllers/v3_controller.rb +++ b/app/controllers/v3_controller.rb @@ -1,9 +1,63 @@ # frozen_string_literal: true class V3Controller < ApplicationController - check_authorization + include EcdsRailsAuthEngine::CurrentUser + before_action :allowed?, only: [:show, :create, :update, :destroy] + before_action :set_record, only: [:show, :update, :destroy] - rescue_from CanCan::AccessDenied do |exception| - head 401 + # GET //1 + def show + render json: @record end + + # POST /v3/tour_media + def create + render json: {}, status: :unauthorized + end + + # PATCH/PUT /media/1 + def update + if crud_allowed? + if @record.update(record_params) + render json: @record + else + render json: serialize_errors, status: :unprocessable_entity + end + else + render json: {}, status: :unauthorized + end + end + + def destroy + if crud_allowed? + @record.destroy + else + render json: {}, status: :unauthorized + end + end + + def serialize_errors + errors = [] + if @record&.errors + @record.errors.full_messages.each do |error| + errors.push({ + detail: error, + source: { + pointer: 'data/attributes' + } + }) + end + end + { errors: errors } + end + + private + + def allowed? + @allowed = @record&.published || crud_allowed? + end + + def crud_allowed? + current_user&.current_tenant_admin? || current_user.tours.present? + end end diff --git a/app/models/ability.rb b/app/models/ability.rb deleted file mode 100644 index 1817308e..00000000 --- a/app/models/ability.rb +++ /dev/null @@ -1,45 +0,0 @@ -# frozen_string_literal: true - -class Ability - include CanCan::Ability - - def initialize(user) - can :read, Tour, published: true - can :read, Slug, published: true - can :read, Theme - can :read, Mode - can :read, TourMode - can :read, Stop - can :read, TourStop - can :read, TourMedium - can :read, StopMedium - can :read, Medium - can :read, FlatPage - can [:read], TourSet - return unless user.id.present? - can [:read, :edit, :update], Tour - can [:manage], TourMedium - can [:manage], Medium - can [:manage], TourStop - can [:manage], Stop - can [:manage], StopMedium - can [:manage], FlatPage - can [:manage], TourFlatPage - can [:manage], Mode - can [:manage], TourMode - can [:read], User - return unless user.current_tenant_admin? - can [:manage], Tour - can [:manage], TourSetAdmin - can [:read, :edit, :update], TourSet - can [:read, :edit, :update], User - # can :manage, Stop - # can :manage, TourStop - # can :manage, TourMedium - # can :manage, StopMedium - # can :manage, Medium - # can :manage, FlatPage - return unless user.super - can :manage, :all - end -end diff --git a/app/models/concerns/video_props.rb b/app/models/concerns/video_props.rb index bc82c22c..619b72e6 100644 --- a/app/models/concerns/video_props.rb +++ b/app/models/concerns/video_props.rb @@ -1,79 +1,83 @@ # frozen_string_literal: true +require 'open-uri' + module VideoProps extend ActiveSupport::Concern def self.props(medium) return if medium.video.nil? - if self.is_vimeo(medium) - medium.provider = 'vimeo' - medium.video = vimeo_id(medium) + case medium.video_provider + when 'keiner' + nil + when 'vimeo' metadata = HTTParty.get("https://vimeo.com/api/oembed.json?url=https%3A//vimeo.com/video/#{medium.video}") medium.title = metadata['title'] medium.caption = metadata['description'] - image = metadata['thumbnail_url'] - medium.remote_original_image_url = metadata['thumbnail_url'] - medium.embed = "" - - elsif self.is_youtube(medium) - medium.provider = 'youtube' - medium.video = youtube_id(medium) + medium.embed = "//player.vimeo.com/video/#{medium.video}" + thumbnail_width = metadata['thumbnail_width'] + thumbnail_height = metadata['thumbnail_height'] + scale_by = 1000 / thumbnail_width + thumbnail_url = "#{metadata['thumbnail_url'].split('_')[0]}_#{thumbnail_width * scale_by}x#{thumbnail_height * scale_by}" + downloaded_image = URI.open(thumbnail_url) + when 'youtube' begin metadata = Yt::Video.new(id: medium.video) medium.title = metadata.title medium.caption = metadata.description - medium.remote_original_image_url = "https://img.youtube.com/vi/#{medium.video}/0.jpg" - medium.embed = %Q[" - # end - # medium.save - # end - - # - - def self.is_youtube(medium) - if medium.provider == 'vimeo' - return false end - # FIXME Youtube is always going to return 200 - (medium.video.include? 'youtube.com') || (medium.video.include? 'youtu.be') || (!medium.video.include?('iframe') && HTTParty.get("https://www.youtube.com/watch?v=#{medium.video}").code == 200) - end - def self.is_vimeo(medium) - if medium.provider == 'youtube' - return false - end - (medium.video.include? 'vimeo') || (!medium.video.include?('iframe') && HTTParty.get("https://vimeo.com/#{medium.video}").code == 200) - end + return if downloaded_image.nil? - def self.youtube_id(medium) - if medium.video.include? 'iframe' - YouTubeRails.extract_video_id(Nokogiri::HTML(medium.video).xpath('//iframe')[0]['src']) - elsif medium.video.include?('youtu') - YouTubeRails.extract_video_id(medium.video) - else - medium.video - end + medium.filename = "#{medium.video}.jpg" + medium.base_sixty_four = encode_image(downloaded_image) + medium.attach_file unless medium.file.attached? end - def self.vimeo_id(medium) - if medium.video.include? 'iframe' - Nokogiri::HTML(medium.video).xpath('//iframe')[0]['src'].split('/')[-1] - else - /\d{7,10}/.match(medium.video)[0] - # /https?:\/\/{?:www\.}?vimeo.com\/{?:channels\/(?:\w+\/)?|groups\/([^/]*)\/videos\/|album\/(\d+)\/video\/|)(\d+)(?:$|\/|\?)/.match(medium.video)[0] + def self.encode_image(downloaded_image) + begin + if downloaded_image.is_a? StringIO + base_sixty_four = Base64.encode64(downloaded_image.read) + else + base_sixty_four = Base64.encode64(downloaded_image.open.read) + downloaded_image.unlink + end + rescue NoMethodError + base_sixty_four = Base64.encode64(downloaded_image) end + base_sixty_four end end diff --git a/app/models/flat_page.rb b/app/models/flat_page.rb index 1eb2e524..cba879a5 100644 --- a/app/models/flat_page.rb +++ b/app/models/flat_page.rb @@ -4,4 +4,16 @@ class FlatPage < ApplicationRecord has_many :tour_flat_pages has_many :tours, through: :tour_flat_pages validates :title, presence: true + + def slug + title ? title.parameterize_intl : '' + end + + def orphaned + tours.empty? + end + + def published + tours.any? { |tour| tour.published } + end end diff --git a/app/models/map_icon.rb b/app/models/map_icon.rb new file mode 100644 index 00000000..e5376c03 --- /dev/null +++ b/app/models/map_icon.rb @@ -0,0 +1,19 @@ +class MapIcon < MediumBaseRecord + validate :check_dimensions + + has_one :stop + + def published + stop.published + end + + def check_dimensions + return if base_sixty_four.nil? + + file = MiniMagick::Image.read(Base64.decode64(base_sixty_four)) + + if file[:height] > 80 || file[:width] > 80 + errors.add(:base, 'Icons should be no bigger that 80 by 80 pixels') + end + end +end diff --git a/app/models/map_overlay.rb b/app/models/map_overlay.rb new file mode 100644 index 00000000..7fca50c3 --- /dev/null +++ b/app/models/map_overlay.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +# +# Model calss for map overlays. +# +class MapOverlay < MediumBaseRecord + before_create :set_initial_bounds + + belongs_to :tour, optional: true + belongs_to :stop, optional: true + + def published + tour.published + end + + def set_initial_bounds + return if tour&.bounds.nil? || tour&.stop_count < 2 + + if tour + self.south = self.tour.bounds[:south] + self.north = self.tour.bounds[:north] + self.east = self.tour.bounds[:east] + self.west = self.tour.bounds[:west] + end + end +end diff --git a/app/models/medium.rb b/app/models/medium.rb index 9c43678d..48cdca2c 100644 --- a/app/models/medium.rb +++ b/app/models/medium.rb @@ -1,84 +1,75 @@ # frozen_string_literal: true # Model for media associated with stops. -class Medium < ApplicationRecord +class Medium < MediumBaseRecord include VideoProps include Rails.application.routes.url_helpers - before_validation :props - mount_base64_uploader :original_image, MediumUploader + before_create :props + before_save :add_widths + before_update :replace_video + + # has_one_attached :file do |attachable| + # attachable.variant :mobile, resize: '200x200' + # attachable.variant :tablet, resize: '300x300' + # attachable.variant :desktop, resize: '750x750' + # end + + # mount_base64_uploader :original_image, MediumUploader has_many :stop_media has_many :stops, through: :stop_media has_many :tour_media has_many :tours, through: :tour_media - validates_presence_of :original_image + enum video_provider: { keiner: 0, vimeo: 1, youtube: 2, soundcloud: 3 } attr_accessor :insecure - # TODO: This is not ideal, we use these `not_in_*` scopes to make the list of media avaliable to add - # to a stop or tour. But the paramerter does not make sense when just looking at it. Needs clearer language. - scope :not_in_stop, lambda { |stop_id| includes(:stop_media).where.not(stop_media: { stop_id: stop_id }) } - scope :not_in_tour, lambda { |tour_id| includes(:tour_media).where.not(tour_media: { tour_id: tour_id }) } - scope :no_stops, lambda { includes(:stop_media).where(stop_media: { stop_id: nil }) } - scope :no_tours, lambda { includes(:tour_media).where(tour_media: { tour_id: nil }) } - scope :orphan, -> { no_tours.no_stops } - scope :published_by_tour, lambda { includes(:tours).where(tours: { published: true }) } - scope :published_by_stop, -> { joins(:stops).merge(Stop.published) } - - def props return if self.video.nil? || self.video.empty? VideoProps.props(self) end - def desktop - original_image.desktop.url - end - - def tablet - original_image.tablet.url - end - - def mobile - original_image.mobile_list_thumb.url - end - - def mobile_thumb - original_image.mobile_list_thumb.url - end - def published - # This works and is shorter, but I think the longer way is more readable/clear. - # tours.published.present? || stops { |s| s.tours.published }.present? - tours.collect(&:published).include?(true) || stops.map {|s| s.tours.collect(&:published)}.flatten.include?(true) + tours.any? { |tour| tour.published } || stops.any? { |stop| stop.published } end - def srcset - "#{ENV['BASE_URL']}#{self.mobile} #{mobile_width}w, \ - #{ENV['BASE_URL']}#{self.tablet} #{tablet_width}w, \ - #{ENV['BASE_URL']}#{self.desktop} #{desktop_width}w" - end + def files + return nil if !self.file.attached? - def srcset_sizes - "(max-width: 680px) #{mobile_width}px, (max-width: 880px) #{tablet_width}px, #{desktop_width}px" + if file.content_type.include?('gif') + return { + lqip: file.variant(resize_to_limit: [50, 50], coalesce: true, layers: 'Optimize', deconstruct: true, loader: { page: nil }).processed.url, + mobile: file.variant(resize_to_limit: [300, 300], coalesce: true, layers: 'Optimize', deconstruct: true, loader: { page: nil }).processed.url, + tablet: file.variant(resize_to_limit: [400, 400], coalesce: true, layers: 'Optimize', deconstruct: true, loader: { page: nil }).processed.url, + desktop: file.variant(resize_to_limit: [750, 750], coalesce: true, layers: 'Optimize', deconstruct: true, loader: { page: nil }).processed.url + } + end + { + lqip: file.variant(resize_to_limit: [5, 5]).processed.url, + mobile: file.variant(resize_to_limit: [300, 300]).processed.url, + tablet: file.variant(resize_to_limit: [400, 400]).processed.url, + desktop: file.variant(resize_to_limit: [750, 750]).processed.url + } end - def insecure - "#{ENV['INSECURE_IMAGE_BASE_URL']}#{self.desktop}" + def orphaned + tours.empty? && stops.empty? end - def base64 - if self.original_image.file && File.file?(self.original_image.file.path) - return Base64.encode64(self.original_image.file.read) + def replace_video + if video.present? && base_sixty_four.present? + attach_file end - nil end - # private + def add_widths + return unless file.attached? - # def has_image - # original_image.present? || remote_original_image_url.present? - # end -end \ No newline at end of file + self.lqip_width = MiniMagick::Image.open(files[:lqip])[:width] || 50 + self.mobile_width = MiniMagick::Image.open(files[:mobile])[:width] || 300 + self.tablet_width = MiniMagick::Image.open(files[:tablet])[:width] || 400 + self.desktop_width = MiniMagick::Image.open(files[:desktop])[:width] || 750 + end +end diff --git a/app/models/medium_base_record.rb b/app/models/medium_base_record.rb new file mode 100755 index 00000000..64f0e8c9 --- /dev/null +++ b/app/models/medium_base_record.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +# Base class for models. +class MediumBaseRecord < ApplicationRecord + self.abstract_class = true + validate :check_content_type + + before_create :attach_file + before_destroy :purge + + validates_presence_of :filename + + # has_one_attached "#{Apartment::Tenant.current.underscore}_file" + has_one_attached 'file' + + attr_accessor :content_type + + # def image_url + # return nil unless file.attached? + + # file.url + # end + + def tmp_file_path + return Rails.root.join('public', 'storage', 'tmp', filename) if self.filename + nil + end + + def original_image_url + file.url + end + + # + # Create and attach file from Base64 string. + # + # This should only be called once when a new medium obeject is created via the API + # It assumes + # + # Some code taken from https://github.com/rootstrap/active-storage-base64/blob/v1.2.0/lib/active_storage_support/base64_attach.rb#L17-L32 + # + # + def attach_file + return if base_sixty_four.nil? + + # file.blob.delete if file.attached? + + self.parse_base64 + File.open(tmp_file_path, 'wb') do |f| + f.write(Base64.decode64(base_sixty_four)) + end + + self.file.attach( + io: File.open(tmp_file_path), + filename: filename, + content_type: self.content_type + ) + + self.base_sixty_four = nil + end + + def remove_tmp_file + File.delete(tmp_file_path) if File.exists?(tmp_file_path) + end + + def purge + remove_tmp_file + file.blob.delete if file.attached? + end + + def check_content_type + return if base_sixty_four.nil? + + self.parse_base64 + + if self.content_type.include?('jp2') + errors.add(:base, 'JPEG 2000 fils are not supported. Plese convert the image to a reqular JPEG or WebP format.') + end + end + + private + + def parse_base64 + if base_sixty_four.include?('data:') + headers, self.base_sixty_four = base_sixty_four.split(',') + headers =~ /^data:(.*?)$/ + self.content_type = Regexp.last_match(1).split(';base64').first + else + self.content_type = 'image/jpeg' + end + end +end diff --git a/app/models/slug.rb b/app/models/slug.rb index 9fbc0d78..f4d062c9 100644 --- a/app/models/slug.rb +++ b/app/models/slug.rb @@ -4,9 +4,9 @@ class Slug < ApplicationRecord belongs_to :tour validates :slug, uniqueness: true - attr_accessor :published + # attr_accessor :published - def published - tour.published - end + # def published + # tour.published + # end end diff --git a/app/models/stop.rb b/app/models/stop.rb index afde7bd6..3069a1c1 100644 --- a/app/models/stop.rb +++ b/app/models/stop.rb @@ -9,19 +9,17 @@ class Stop < ApplicationRecord has_many :stop_media has_many :media, through: :stop_media belongs_to :medium, optional: true + belongs_to :map_icon, optional: true has_many :stop_slugs, dependent: :delete_all + before_validation -> { self.title ||= 'untitled' } + validates :title, presence: true - # validates :title, uniqueness: true after_initialize :default_values + before_create :ensure_icon_color after_save :ensure_slug - before_validation -> { self.title ||= 'untitled' } - - scope :not_in_tour, lambda { |tour_id| includes(:tour_stops).where.not(tour_stops: { tour_id: tour_id }) } - scope :no_tours, lambda { includes(:tour_stops).where(tour_stops: { tour_id: nil }) } - scope :published, lambda { includes(:tours).where(tours: { published: true }) } scope :by_slug_and_tour, lambda { |slug, tour_id| joins(:stop_slugs).joins(:tours).where('stop_slugs.slug = ?', slug).where('tour_stops.tour_id = ?', tour_id) } def sanitized_description @@ -33,37 +31,31 @@ def sanitized_direction_notes end def slug - title.parameterize + title ? title.parameterize_intl : '' end def splash - if medium.present? - return medium + splash_medium = if medium.present? + medium elsif stop_media.present? - return stop_media.order(:position).first.medium + stop_media.order(:position).first.medium + else + nil end - nil - end - def splash_height - splash.nil? ? nil : splash.desktop_height - end - - def splash_width - splash.nil? ? nil : splash.desktop_width - end - - def insecure_splash - if !stop_media.empty? - return medium.nil? ? stop_media.order(:position).first.medium.insecure : medium.insecure + if splash_medium&.files + return { title: splash_medium.title, caption: splash_medium.caption, url: splash_medium.files[:desktop] } end nil end - def is_published - tours.published.present? + def orphaned + tours.empty? end + def published + tours.any? { |tour| tour.published } + end private @@ -72,6 +64,10 @@ def default_values end def ensure_slug - StopSlug.find_or_create_by(slug: self.slug, stop: self) + tour_stops.each { |ts| ts.save } + end + + def ensure_icon_color + self.icon_color = '#D32F2F' if icon_color.nil? end end diff --git a/app/models/stop_medium.rb b/app/models/stop_medium.rb index df0c21fc..b8980451 100644 --- a/app/models/stop_medium.rb +++ b/app/models/stop_medium.rb @@ -5,16 +5,20 @@ class StopMedium < ApplicationRecord belongs_to :medium belongs_to :stop + def published + stop&.published + end + after_create do self.position = self.position.nil? ? self.stop.media.length : self.position self.save end - before_destroy do - if self.medium.nil? || self.stop.nil? - nil - else - self.medium.stops.length == 1 ? self.medium.destroy! : nil - end - end + # before_destroy do + # if self.medium.nil? || self.stop.nil? + # nil + # else + # self.medium.stops.length == 1 ? self.medium.destroy! : nil + # end + # end end diff --git a/app/models/stop_slug.rb b/app/models/stop_slug.rb index 44a5c955..332f30e9 100644 --- a/app/models/stop_slug.rb +++ b/app/models/stop_slug.rb @@ -1,4 +1,5 @@ class StopSlug < ApplicationRecord belongs_to :stop + belongs_to :tour # validates :slug, uniqueness: true end diff --git a/app/models/stop_tag.rb b/app/models/stop_tag.rb deleted file mode 100644 index 4172fffa..00000000 --- a/app/models/stop_tag.rb +++ /dev/null @@ -1,6 +0,0 @@ -# frozen_string_literal: true - -# app/models/stop_tag.rb -class StopTag < ApplicationRecord - validates :title, presence: true -end diff --git a/app/models/tour.rb b/app/models/tour.rb index ecb563e5..d6116d49 100644 --- a/app/models/tour.rb +++ b/app/models/tour.rb @@ -1,11 +1,15 @@ # frozen_string_literal: true +require 'uri' + # Model class for a tour. class Tour < ApplicationRecord include HtmlSaintizer + + has_many :tour_stops, autosave: true, dependent: :destroy has_many :stops, -> { distinct }, through: :tour_stops - has_many :tour_modes + has_many :tour_modes, autosave: true, dependent: :destroy has_many :modes, through: :tour_modes belongs_to :mode, default: -> { Mode.last } has_many :tour_media @@ -14,28 +18,45 @@ class Tour < ApplicationRecord has_many :tour_flat_pages has_many :flat_pages, through: :tour_flat_pages has_many :tour_authors - has_many :authors, through: :tour_authors, source: :user + has_many :users, through: :tour_authors has_many :slugs, dependent: :delete_all - # has_many :authors, through: :tour_authors, foreign_key: :user_id + has_one :map_overlay + + # TODO: why does the CircleCI env need to serialize here? + if ENV['CI'] == 'circleci' + serialize :saved_stop_order, Array + end # belongs_to :splash_image_medium_id, class_name: 'Medium' belongs_to :theme, default: -> { Theme.first } - validates :title, presence: true + + enum default_lng: { + "en-US": 0, "fr-FR": 1, "de-DE": 2, "pl-PL": 3, "nl-NL": 4, "fi-FI": 5, "sv-SE": 6, "it-IT": 7, "es-ES": 8, "pt-PT": 9, + "ru-RU": 10, "pt-BR": 11, "es-MX": 12, "zh-CN": 13, "zh-TW": 14, "ja-JP": 15, "ko-KR": 16 + } + + validates :title, presence: true, uniqueness: { case_sensitive: false } before_validation -> { self.mode ||= Mode.last } before_validation -> { self.theme ||= Theme.first } before_validation -> { self.title ||= 'untitled' } + before_validation :update_saved_stop_order + before_save :calculate_duration + before_save :check_url + before_save :check_for_overlay after_save :ensure_slug after_create :add_modes scope :published, -> { where(published: true) } + scope :mapable, -> { where(is_geo: true) } + scope :has_stops, -> { includes(:stops).where.not(stops: { id: nil }) } def sanitized_description HtmlSaintizer.accessable(description) end def slug - title.parameterize + title.parameterize_intl end def tenant @@ -58,31 +79,80 @@ def theme_title end def splash - if medium.present? - return medium + splash_medium = if medium.present? + medium elsif tour_media.present? - return tour_media.order(:position).first.medium + tour_media.order(:position).first.medium + else + nil + end + + if splash_medium + return { title: splash_medium.title, caption: splash_medium.caption, url: splash_medium.files[:desktop] } end nil end - def splash_height - splash.nil? ? nil : splash.desktop_height + def stop_count + self.stops.count end - def splash_width - splash.nil? ? nil : splash.desktop_width + def bounds + if self.restrict_bounds_to_overlay && self.map_overlay.present? + box = RGeo::Cartesian::BoundingBox.create_from_points( + RGeo::Geographic.spherical_factory.point(self.map_overlay.east.to_f, self.map_overlay.south.to_f), + RGeo::Geographic.spherical_factory.point(self.map_overlay.west.to_f, self.map_overlay.north.to_f) + ) + + return { + south: box.min_y - (box.y_span / 8), + north: box.max_y + (box.y_span / 8), + east: box.max_x + (box.x_span / 8), + west: box.min_x - (box.x_span / 8), + centerLat: box.center_y, + centerLng: box.center_x + } + elsif stops.empty? + return nil + end + + points = stops.map { |stop| RGeo::Geographic.spherical_factory.point(stop.lng, stop.lat) } + box = RGeo::Cartesian::BoundingBox.create_from_points(points.pop, points.pop) + points.each { |point| box.add(point) } + + { + south: box.min_y - (box.y_span / 8), + north: box.max_y + (box.y_span / 8), + east: box.max_x + (box.x_span / 8), + west: box.min_x - (box.x_span / 8), + centerLat: box.center_y, + centerLng: box.center_x + } end - def insecure_splash - if !tour_media.empty? - return medium.nil? ? tour_media.order(:position).first.medium.insecure : medium.insecure + def calculate_duration + return unless published + + return if stops.count < 2 + + return if mode.nil? + + return if mode.title.nil? + + return unless self.will_save_change_to_published? || self.will_save_change_to_saved_stop_order? || self.will_save_change_to_mode_id? + + durations = [] + destinations = tour_stops.order(:position).map { |tour_stop| [tour_stop.stop.lat, tour_stop.stop.lng] } + + # The direction matrix API limits the number of destinations to 25. + # Calculate the duration in chunks to stay below the limit. + destinations.each_slice(24) do |group| + origin = group.shift + g_directions = GoogleDirections.new(origin, group, group.count + 1, mode.title) + durations.push(g_directions.duration) end - nil - end - def stop_count - self.stops.count + self.duration = durations.compact.sum.zero? ? nil : durations.sum end private @@ -96,4 +166,31 @@ def add_modes self.modes << m end end + + def check_url + return if link_address.blank? + + uri = URI(link_address) + + self.link_address = "http://#{link_address}" if uri.scheme.nil? + end + + def update_saved_stop_order + self.saved_stop_order = self.tour_stops.order(:position).map(&:stop_id) + end + + def check_for_overlay + if self.restrict_bounds_to_overlay && self.map_overlay.nil? + self.restrict_bounds_to_overlay = false + # self.restrict_bounds = false + end + + if !self.restrict_bounds_to_overlay_was && self.restrict_bounds_to_overlay && self.map_overlay.present? + self.restrict_bounds = false + end + + if self.restrict_bounds && !self.restrict_bounds_was + self.restrict_bounds_to_overlay = false + end + end end diff --git a/app/models/tour_collection.rb b/app/models/tour_collection.rb deleted file mode 100644 index 17092ecc..00000000 --- a/app/models/tour_collection.rb +++ /dev/null @@ -1,4 +0,0 @@ -# frozen_string_literal: true - -class TourCollection < ApplicationRecord -end diff --git a/app/models/tour_medium.rb b/app/models/tour_medium.rb index 2996f8a0..13c44b5b 100644 --- a/app/models/tour_medium.rb +++ b/app/models/tour_medium.rb @@ -11,11 +11,15 @@ class TourMedium < ApplicationRecord self.save end - before_destroy do - if self.medium.nil? || self.tour.nil? - nil - else - self.medium.tours.length == 1 ? self.medium.destroy! : nil - end + # before_destroy do + # if self.medium.nil? || self.tour.nil? + # nil + # else + # self.medium.tours.length == 1 ? self.medium.destroy! : nil + # end + # end + + def published + tour&.published end end diff --git a/app/models/tour_set.rb b/app/models/tour_set.rb index c1ccefc7..ad205817 100644 --- a/app/models/tour_set.rb +++ b/app/models/tour_set.rb @@ -2,25 +2,26 @@ # Model class for tour sets. This is the main model for "instances" of Open Tour Builder. class TourSet < ApplicationRecord - mount_uploader :logo, LogoUploader - mount_uploader :footer_logo, LogoFooterUploader - has_many :tour_set_admins - has_many :admins, through: :tour_set_admins, source: :user before_save :set_subdir + before_save :attach_file after_create :create_tenant after_create :create_defaults before_destroy :drop_tenant + validates :name, presence: true, uniqueness: true - # attr_accessor :footer_logo_width - # attr_accessor :footer_logo_height + + has_one_attached 'logo' + + has_many :tour_set_admins + has_many :admins, through: :tour_set_admins, source: :user + attr_accessor :published_tours - # validate :validate_footer_logo_dimensions, if :uploading? def published_tours begin Apartment::Tenant.switch! self.subdir tours = [] - Tour.published.each do |t| + Tour.published.has_stops.each do |t| tour = { title: t.title, slug: t.slug @@ -33,10 +34,39 @@ def published_tours end end + def mapable_tours + begin + Apartment::Tenant.switch! self.subdir + tours = [] + Tour.published.has_stops.mapable.each do |t| + tour = { + title: t.title, + slug: t.slug, + center: { lat: t.bounds[:centerLat], lng: t.bounds[:centerLng] } + } + tours.push(tour) + end + tours + rescue Apartment::TenantNotFound => error + # self.delete + end + end + + def logo_url + Apartment::Tenant.switch! 'public' + begin + return logo.url if logo.attached? + rescue URI::InvalidURIError + # FIXME: This seems to be a problem when testing? + end + + nil + end + private def set_subdir - self.subdir = name.parameterize + self.subdir = name.parameterize_intl end def create_tenant @@ -57,8 +87,8 @@ def create_tenant end def create_defaults - Apartment::Tenant.reset - themes = Theme.all.collect(&:title) + # Apartment::Tenant.reset + # themes = Theme.all.collect(&:title) Apartment::Tenant.switch! subdir Mode.create([ { title: 'BICYCLING', icon: 'bicycle' }, @@ -66,31 +96,58 @@ def create_defaults { title: 'TRANSIT', icon: 'subway' }, { title: 'WALKING', icon: 'walking' } ]) - themes.each do |t| - Theme.create(title: t) - end + # themes.each do |t| + # Theme.create(title: t) + # end end def drop_tenant Apartment::Tenant.drop(subdir) end - def symlink_logo - FileUtils.mkdir "#{Rails.root}/public/uploads/#{self.subdir}" - FileUtils.ln_s "#{Rails.root}/public/otblogo.png", - "#{Rails.root}/public/uploads/#{self.subdir}/otblogo.png" - self.logo = 'otblogo.png' - self.footer_logo = 'otblogo.png' - end + def tmp_file_path + return nil if logo_title.nil? - def uploading? - footer_logo_width.present? && footer_logo_height.present? + Rails.root.join('public', 'storage', 'tmp', logo_title) end - def validate_footer_logo_dimensions - ::Rails.logger.info "Footer logo upload dimensions: #{self.footer_logo_width}x#{self.footer_logo_height}" - if self.footer_logo_width != footer_logo_height - errors.add :footer_logo, 'Footer logo should be square' + # + # Create and attach file from Base64 string. + # + # This should only be called once when a new medium obeject is created via the API + # It assumes + # + # Some code taken from https://github.com/rootstrap/active-storage-base64/blob/v1.2.0/lib/active_storage_support/base64_attach.rb#L17-L32 + # + # + def attach_file + return if base_sixty_four.nil? && !logo.attached? + + + return if !self.will_save_change_to_base_sixty_four? && logo.attached? + + if base_sixty_four.nil? && logo.attached? + logo.purge + else + headers, self.base_sixty_four = base_sixty_four.split(',') + + return if base_sixty_four.nil? + + File.open(tmp_file_path, 'wb') do |f| + f.write(Base64.decode64(base_sixty_four)) + end + + image = MiniMagick::Image.open(tmp_file_path) + + if image[:height] > 80 + image.resize('300x80') + image.write(tmp_file_path) + end + + self.logo.attach( + io: File.open(tmp_file_path), + filename: logo_title + ) end end end diff --git a/app/models/tour_stop.rb b/app/models/tour_stop.rb index 9f4d5769..b47b61c4 100644 --- a/app/models/tour_stop.rb +++ b/app/models/tour_stop.rb @@ -7,8 +7,8 @@ class TourStop < ApplicationRecord validates :position, presence: true + before_save :_ensure_stop_slug before_validation :_set_position - before_destroy :_delete_orphan def slug stop.slug @@ -37,12 +37,14 @@ def previous_slug private def _set_position + return if tour.nil? + self.position = self.position || self.tour.stops.length + 1 end - def _delete_orphan - if self.stop.tours.length == 1 - self.stop.destroy - end + def _ensure_stop_slug + new_slug = StopSlug.find_or_create_by(slug: self.stop.slug, tour: self.tour) + new_slug.stop = self.stop + new_slug.save end end diff --git a/app/models/tour_tag.rb b/app/models/tour_tag.rb deleted file mode 100644 index f69822fd..00000000 --- a/app/models/tour_tag.rb +++ /dev/null @@ -1,6 +0,0 @@ -# frozen_string_literal: true - -# app/models/tour_tag.rb -class TourTag < ApplicationRecord - validates :title, presence: true -end diff --git a/app/models/user.rb b/app/models/user.rb index 581c5825..1ce93c2a 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,13 +1,14 @@ # frozen_string_literal: true class User < ActiveRecord::Base - include EcdsRailsAuthEngine::EcdsUser has_many :tour_set_admins has_many :tour_sets, through: :tour_set_admins has_many :tour_authors has_many :tours, through: :tour_authors - scope :search, -> (search) { joins(:login).where("users.display_name ILIKE '%#{search}%' OR logins.identification ILIKE '%#{search}%'")} + validates :email, presence: true + + # scope :search, -> (search) { joins(:login).where("users.display_name ILIKE '%#{search}%' OR logins.identification ILIKE '%#{search}%'")} # # Gets role for current tenant @@ -17,8 +18,28 @@ class User < ActiveRecord::Base def current_tenant_admin? return true if self.super return false if tour_sets.empty? - tour_sets.collect(&:subdir).include? Apartment::Tenant.current + tour_sets.map(&:subdir).include? Apartment::Tenant.current end -end + def provider + return nil if login.nil? + login.provider + end + def all_tours + all = [] + TourSet.all.each do |tour_set| + Apartment::Tenant.switch! tour_set.subdir + next if tours.empty? || current_tenant_admin? + Apartment::Tenant.switch! tour_set.subdir + _tours = TourAuthor.where(user: self) + all.push(_tours.map { |ta| { id: ta.tour.id, tenant: ta.tour.tenant, title: ta.tour.title } }) + end + Apartment::Tenant.reset + all.flatten.uniq + end + + def login + EcdsRailsAuthEngine::Login.find_by(user_id: self.id) + end +end diff --git a/app/models/v3.rb b/app/models/v3.rb deleted file mode 100644 index b31800ab..00000000 --- a/app/models/v3.rb +++ /dev/null @@ -1,5 +0,0 @@ -module V3 - def self.table_name_prefix - 'v3_' - end -end diff --git a/app/serializers/v3/flat_page_serializer.rb b/app/serializers/v3/flat_page_serializer.rb index e386865b..52fb55c0 100644 --- a/app/serializers/v3/flat_page_serializer.rb +++ b/app/serializers/v3/flat_page_serializer.rb @@ -1,3 +1,6 @@ -class V3::FlatPageSerializer < ActiveModel::Serializer - attributes :id, :title, :content +module V3 + class FlatPageSerializer < ActiveModel::Serializer + has_many :tours + attributes :id, :title, :body, :slug, :orphaned + end end diff --git a/app/serializers/v3/map_icon_serializer.rb b/app/serializers/v3/map_icon_serializer.rb new file mode 100644 index 00000000..1fdf2c28 --- /dev/null +++ b/app/serializers/v3/map_icon_serializer.rb @@ -0,0 +1,6 @@ +module V3 + class MapIconSerializer < ActiveModel::Serializer + include Rails.application.routes.url_helpers + attributes :id, :base_sixty_four, :filename, :original_image_url + end +end diff --git a/app/serializers/v3/map_overlay_serializer.rb b/app/serializers/v3/map_overlay_serializer.rb new file mode 100644 index 00000000..94446148 --- /dev/null +++ b/app/serializers/v3/map_overlay_serializer.rb @@ -0,0 +1,6 @@ +module V3 + class MapOverlaySerializer < ActiveModel::Serializer + include Rails.application.routes.url_helpers + attributes :id, :south, :north, :east, :west, :original_image_url, :filename + end +end diff --git a/app/serializers/v3/medium_serializer.rb b/app/serializers/v3/medium_serializer.rb index a6a5283a..7cbc4f87 100644 --- a/app/serializers/v3/medium_serializer.rb +++ b/app/serializers/v3/medium_serializer.rb @@ -1,5 +1,31 @@ # frozen_string_literal: true -class V3::MediumSerializer < ActiveModel::Serializer - attributes :id, :title, :caption, :desktop, :tablet, :mobile, :video, :provider, :original_image, :embed, :srcset, :srcset_sizes, :insecure, :base64 +module V3 + class MediumSerializer < ActiveModel::Serializer + # include Rails.application.routes.url_helpers + attributes :id, + :title, + :caption, + :video, + :provider, + :original_image, + :embed, + :files, + :orphaned, + :filename, + :original_image_url, + :lqip_width, + :mobile_width, + :tablet_width, + :desktop_width + + # def files + # return nil unless object.file.attached? + # { + # mobile: Rails.application.routes.url_helpers.rails_representation_url(object.file.variant(resize: '200x200').processed), + # tablet: Rails.application.routes.url_helpers.rails_representation_url(object.file.variant(resize: '300x300').processed), + # desktop: Rails.application.routes.url_helpers.rails_representation_url(object.file.variant(resize: '750x750').processed) + # } + # end + end end diff --git a/app/serializers/v3/mode_serializer.rb b/app/serializers/v3/mode_serializer.rb index 755721b5..f5e7a842 100644 --- a/app/serializers/v3/mode_serializer.rb +++ b/app/serializers/v3/mode_serializer.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true -class V3::ModeSerializer < ActiveModel::Serializer - has_many :tours - attributes :id, :title, :icon +module V3 + class ModeSerializer < ActiveModel::Serializer + has_many :tours + attributes :id, :title, :icon + end end diff --git a/app/serializers/v3/stop_medium_serializer.rb b/app/serializers/v3/stop_medium_serializer.rb index 866a4bc2..c925d22b 100644 --- a/app/serializers/v3/stop_medium_serializer.rb +++ b/app/serializers/v3/stop_medium_serializer.rb @@ -1,7 +1,9 @@ # frozen_string_literal: true -class V3::StopMediumSerializer < ActiveModel::Serializer - belongs_to :stop - belongs_to :medium - attributes :id, :position +module V3 + class StopMediumSerializer < ActiveModel::Serializer + belongs_to :stop + belongs_to :medium + attributes :id, :position + end end diff --git a/app/serializers/v3/stop_serializer.rb b/app/serializers/v3/stop_serializer.rb index 648c1679..767923ac 100644 --- a/app/serializers/v3/stop_serializer.rb +++ b/app/serializers/v3/stop_serializer.rb @@ -1,8 +1,30 @@ # frozen_string_literal: true -class V3::StopSerializer < ActiveModel::Serializer - has_many :media - has_many :stop_media - has_many :tours - attributes :id, :title, :slug, :description, :sanitized_description, :sanitized_direction_notes, :lat, :lng, :address, :meta_description, :article_link, :video_embed, :video_poster, :parking_lat, :parking_lng, :direction_intro, :direction_notes, :splash, :insecure_splash, :splash_width, :splash_height +module V3 + class StopSerializer < ActiveModel::Serializer + has_many :media + has_many :stop_media + has_many :tours + belongs_to :map_icon + attributes :id, + :title, + :slug, + :description, + :sanitized_description, + :sanitized_direction_notes, + :lat, + :lng, + :address, + :meta_description, + :article_link, + :video_embed, + :video_poster, + :parking_lat, + :parking_lng, + :direction_intro, + :direction_notes, + :splash, + :orphaned, + :icon_color + end end diff --git a/app/serializers/v3/theme_serializer.rb b/app/serializers/v3/theme_serializer.rb index 50afdade..88a37ae3 100644 --- a/app/serializers/v3/theme_serializer.rb +++ b/app/serializers/v3/theme_serializer.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true -class V3::ThemeSerializer < ActiveModel::Serializer - attributes :id, :title +module V3 + class ThemeSerializer < ActiveModel::Serializer + attributes :id, :title + end end diff --git a/app/serializers/v3/tour_author_serializer.rb b/app/serializers/v3/tour_author_serializer.rb new file mode 100644 index 00000000..9f8bff5d --- /dev/null +++ b/app/serializers/v3/tour_author_serializer.rb @@ -0,0 +1,7 @@ +module V3 + class TourAuthorSerializer < ActiveModel::Serializer + belongs_to :tour + belongs_to :user + attributes :id + end +end diff --git a/app/serializers/v3/tour_base_serializer.rb b/app/serializers/v3/tour_base_serializer.rb new file mode 100644 index 00000000..13adde0d --- /dev/null +++ b/app/serializers/v3/tour_base_serializer.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +include ActionView::Helpers::DateHelper + +# app/serializers/tour_serializer.rb +module V3 + class TourBaseSerializer < ActiveModel::Serializer + has_one :map_overlay + attributes :id, + :title, + :slug, + :description, + :is_geo, + :published, + :sanitized_description, + :position, + :theme_title, + :meta_description, + :tenant, + :tenant_title, + :stop_count, + :map_type, + :splash, + :use_directions, + :default_lng, + :stop_count, + :est_time, + :link_address, + :link_text, + :restrict_bounds, + :restrict_bounds_to_overlay, + :blank_map + + def est_time + return nil if object.duration.nil? + + "#{distance_of_time_in_words(object.duration).capitalize} #{object.mode.title.downcase}" + end + + def map_type + object.map_type || 'hybrid' + end + + def bounds + return object.bounds if object.bounds.present? + + if @instance_options[:loc].present? + return @instance_options[:loc] + end + + nil + end + end +end diff --git a/app/serializers/v3/tour_collection_serializer.rb b/app/serializers/v3/tour_collection_serializer.rb deleted file mode 100644 index ddf56b81..00000000 --- a/app/serializers/v3/tour_collection_serializer.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -class V3::TourCollectionSerializer < ActiveModel::Serializer - attributes :id -end diff --git a/app/serializers/v3/tour_flat_page_serializer.rb b/app/serializers/v3/tour_flat_page_serializer.rb index 904ccb18..67dfef6f 100644 --- a/app/serializers/v3/tour_flat_page_serializer.rb +++ b/app/serializers/v3/tour_flat_page_serializer.rb @@ -1,5 +1,7 @@ -class V3::TourFlatPageSerializer < ActiveModel::Serializer - belongs_to :tour - belongs_to :flat_page - attributes :id, :position +module V3 + class TourFlatPageSerializer < ActiveModel::Serializer + belongs_to :tour + belongs_to :flat_page + attributes :id, :position + end end diff --git a/app/serializers/v3/tour_medium_serializer.rb b/app/serializers/v3/tour_medium_serializer.rb index 4e67cdf7..465bc51f 100644 --- a/app/serializers/v3/tour_medium_serializer.rb +++ b/app/serializers/v3/tour_medium_serializer.rb @@ -1,7 +1,9 @@ # frozen_string_literal: true -class V3::TourMediumSerializer < ActiveModel::Serializer - belongs_to :tour - belongs_to :medium - attributes :id, :position +module V3 + class TourMediumSerializer < ActiveModel::Serializer + belongs_to :tour + belongs_to :medium + attributes :id, :position + end end diff --git a/app/serializers/v3/tour_mode_serializer.rb b/app/serializers/v3/tour_mode_serializer.rb new file mode 100644 index 00000000..f69200b6 --- /dev/null +++ b/app/serializers/v3/tour_mode_serializer.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module V3 + class TourModeSerializer < ActiveModel::Serializer + belongs_to :tour + belongs_to :mode + attributes :id + end +end diff --git a/app/serializers/v3/tour_modes_serializer.rb b/app/serializers/v3/tour_modes_serializer.rb deleted file mode 100644 index 05e542bf..00000000 --- a/app/serializers/v3/tour_modes_serializer.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -class V3::TourModesSerializer < ActiveModel::Serializer - attributes :id, :tour_id, :mode_id -end diff --git a/app/serializers/v3/tour_serializer.rb b/app/serializers/v3/tour_serializer.rb index a9df2843..052c7f74 100644 --- a/app/serializers/v3/tour_serializer.rb +++ b/app/serializers/v3/tour_serializer.rb @@ -1,15 +1,20 @@ # frozen_string_literal: true # app/serializers/tour_serializer.rb -class V3::TourSerializer < ActiveModel::Serializer - has_many :tour_stops - has_many :stops - belongs_to :mode - belongs_to :theme - has_many :modes - has_many :media - has_many :tour_media - has_many :flat_pages - has_many :tour_flat_pages - attributes :id, :title, :slug, :description, :is_geo, :published, :sanitized_description, :position, :theme_title, :meta_description, :splash, :tenant, :tenant_title, :stop_count, :map_type, :splash_width, :splash_height, :insecure_splash +module V3 + class TourSerializer < V3::TourBaseSerializer + has_many :tour_modes + has_many :tour_stops + has_many :stops + belongs_to :mode + belongs_to :theme + has_many :modes + has_many :media + has_many :tour_media + has_many :flat_pages + has_many :tour_flat_pages + has_many :users + + attributes :bounds + end end diff --git a/app/serializers/v3/tour_set_admin_serializer.rb b/app/serializers/v3/tour_set_admin_serializer.rb index 0efe3f85..4a1508c3 100644 --- a/app/serializers/v3/tour_set_admin_serializer.rb +++ b/app/serializers/v3/tour_set_admin_serializer.rb @@ -1,3 +1,5 @@ -class TourSetAdminSerializer < ActiveModel::Serializer - attributes :id +module V3 + class TourSetAdminSerializer < ActiveModel::Serializer + attributes :id + end end diff --git a/app/serializers/v3/tour_set_serializer.rb b/app/serializers/v3/tour_set_serializer.rb index 5075e24a..497a509d 100644 --- a/app/serializers/v3/tour_set_serializer.rb +++ b/app/serializers/v3/tour_set_serializer.rb @@ -1,11 +1,19 @@ # frozen_string_literal: true -class V3::TourSetSerializer < ActiveModel::Serializer - # attribute :tenant_admins - has_many :admins - attributes :id, :name, :subdir, :published_tours +module V3 + class TourSetSerializer < ActiveModel::Serializer + # attribute :tenant_admins + include Rails.application.routes.url_helpers + has_many :admins + attributes :id, :name, :subdir, :published_tours, :mapable_tours, :logo_url, :logo - def admins - object.admins if current_user.super || current_user.current_tenant_admin? + def admins + begin + object.admins if current_user&.super || current_user&.tour_sets.include?(object) + rescue NameError + # This is a problem when using the serializer directly + nil + end + end end end diff --git a/app/serializers/v3/tour_stop_serializer.rb b/app/serializers/v3/tour_stop_serializer.rb index a0641df9..ad8bef77 100644 --- a/app/serializers/v3/tour_stop_serializer.rb +++ b/app/serializers/v3/tour_stop_serializer.rb @@ -1,8 +1,10 @@ # frozen_string_literal: true # /app/serializers/tour_stop_serializer.rb -class V3::TourStopSerializer < ActiveModel::Serializer - belongs_to :tour - belongs_to :stop - attributes :id, :position, :previous, :slug, :next, :next_slug, :previous_slug +module V3 + class TourStopSerializer < ActiveModel::Serializer + belongs_to :tour + belongs_to :stop + attributes :id, :position, :previous, :slug, :next, :next_slug, :previous_slug + end end diff --git a/app/serializers/v3/user_serializer.rb b/app/serializers/v3/user_serializer.rb index d78c4990..fec6739d 100644 --- a/app/serializers/v3/user_serializer.rb +++ b/app/serializers/v3/user_serializer.rb @@ -1,13 +1,22 @@ # frozen_string_literal: true -# app/serializer/user_serializer.rb -class V3::UserSerializer < ActiveModel::Serializer - has_one :login - has_many :tours - has_many :tour_sets - attributes :id, :display_name, :super, :login, :current_tenant_admin +# app/serializer/v3/user_serializer.rb +module V3 + class UserSerializer < ActiveModel::Serializer + has_many :tours + has_many :tour_authors + has_many :tour_sets + attributes :id, :display_name, :super, :current_tenant_admin, :provider, :email, :all_tours, :terms_accepted - def current_tenant_admin - object.current_tenant_admin? + def current_tenant_admin + object.current_tenant_admin? + end + + def all_tours + if @instance_options[:include_tours] + return object.all_tours + end + [] + end end end diff --git a/app/services/google_directions.rb b/app/services/google_directions.rb new file mode 100644 index 00000000..89547580 --- /dev/null +++ b/app/services/google_directions.rb @@ -0,0 +1,34 @@ +class GoogleDirections + + def initialize(origin, destinations, stops_count, mode) + google_key = ENV['RAILS_ENV'] == 'test' ? 'FAkeFaK-E_fAkeChv-P3nchtQYHoCLfFzn9ylr8' : Rails.application.credentials.dig(:g_maps_key) + @query = { + origins: origin.join(','), + destinations: destinations.map { |d| d.join(',') }.join('|'), + mode: mode, + key: google_key + } + + @stops_count = stops_count + end + + def matrix + response = HTTParty.get( + "https://maps.googleapis.com/maps/api/distancematrix/json?#{@query.to_query}" + ).with_indifferent_access + + return nil if response[:status] && response[:rows].nil? + + return nil if response[:rows].first[:elements].first[:status] == 'ZERO_RESULTS' + + response + end + + def durations + matrix.nil? ? nil : matrix[:rows].first[:elements].map { |e| e[:duration][:value] if e[:duration].present? }.reject { |d| d.nil? } + end + + def duration + durations.nil? ? nil : durations.sum + 600 + (@stops_count * 600) + end +end diff --git a/bin/bundle b/bin/bundle index 58115ecf..f19acf5b 100755 --- a/bin/bundle +++ b/bin/bundle @@ -1,5 +1,3 @@ #!/usr/bin/env ruby -# frozen_string_literal: true - -ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) load Gem.bin_path('bundler', 'bundle') diff --git a/bin/rails b/bin/rails index a3655392..07396602 100755 --- a/bin/rails +++ b/bin/rails @@ -1,11 +1,4 @@ #!/usr/bin/env ruby -# frozen_string_literal: true - -begin - load File.expand_path('../spring', __FILE__) -rescue LoadError => e - raise unless e.message.include?('spring') -end APP_PATH = File.expand_path('../config/application', __dir__) require_relative '../config/boot' require 'rails/commands' diff --git a/bin/rake b/bin/rake index 169c9396..17240489 100755 --- a/bin/rake +++ b/bin/rake @@ -1,11 +1,4 @@ #!/usr/bin/env ruby -# frozen_string_literal: true - -begin - load File.expand_path('../spring', __FILE__) -rescue LoadError => e - raise unless e.message.include?('spring') -end require_relative '../config/boot' require 'rake' Rake.application.run diff --git a/bin/setup b/bin/setup index 1a8bfdd8..a334d86a 100755 --- a/bin/setup +++ b/bin/setup @@ -1,12 +1,9 @@ #!/usr/bin/env ruby -# frozen_string_literal: true - -require 'pathname' require 'fileutils' include FileUtils # path to your application root. -APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) +APP_ROOT = File.expand_path('..', __dir__) def system!(*args) system(*args) || abort("\n== Command #{args} failed ==") @@ -20,7 +17,6 @@ chdir APP_ROOT do system! 'gem install bundler --conservative' system('bundle check') || system!('bundle install') - # puts "\n== Copying sample files ==" # unless File.exist?('config/database.yml') # cp 'config/database.yml.sample', 'config/database.yml' diff --git a/bin/update b/bin/update index fdac831b..67d0d496 100755 --- a/bin/update +++ b/bin/update @@ -1,12 +1,9 @@ #!/usr/bin/env ruby -# frozen_string_literal: true - -require 'pathname' require 'fileutils' include FileUtils # path to your application root. -APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) +APP_ROOT = File.expand_path('..', __dir__) def system!(*args) system(*args) || abort("\n== Command #{args} failed ==") diff --git a/build.sh b/build.sh new file mode 100755 index 00000000..ef706fc2 --- /dev/null +++ b/build.sh @@ -0,0 +1,29 @@ +#!/bin/bash +set -e + +TAG=$([ "$BRANCH" == "main" ] && echo "stable" || echo "latest") + +AWS_ECS_CLUSTER=$([ "$BRANCH" == "main" ] && echo "$AWS_ECS_CLUSTER_PROD" || echo "$AWS_ECS_CLUSTER_DEV") + +AWS_ECS_SERVICE=$([ "$BRANCH" == "main" ] && echo "$AWS_ECS_SERVICE_PROD" || echo "$AWS_ECS_SERVICE_DEV") + +echo "Building image for branch: $BRANCH with tag: $TAG" + +docker build \ + --file Dockerfile \ + -t otb \ + . + +echo "Logging in to AWS" +aws ecr get-login-password --region us-east-1 | + docker login --username AWS --password-stdin "${AWS_ECR}" +echo "Logged in successfully" + +echo "Tagging image with $TAG" +docker tag otb "${AWS_ECR}/otb:${TAG}" + +echo "Pushing image" +docker push "${AWS_ECR}/otb:${TAG}" + +echo "Force update service" +aws ecs update-service --cluster ${AWS_ECS_CLUSTER} --service ${AWS_ECS_SERVICE} --force-new-deployment --region ${AWS_REGION} \ No newline at end of file diff --git a/config/application.rb b/config/application.rb index b6e935f9..3933417c 100644 --- a/config/application.rb +++ b/config/application.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -# /config/application.rb require_relative 'boot' require 'rails' @@ -15,6 +14,8 @@ # require "sprockets/railtie" require 'rails/test_unit/railtie' require 'apartment/elevators/generic' +require 'active_storage/engine' +require 'ipinfo-rails' # Require the gems listed in Gemfile, including any gems # you've limited to :test, :development, or :production. @@ -27,6 +28,10 @@ class DirectoryElevator < Apartment::Elevators::Generic def parse_tenant_name(request) # request is an instance of Rack::Request tenant_name = request.fullpath.split('/')[1] + + if tenant_name == 'auth' || tenant_name == 'rails' + return nil + end tenant_name end end @@ -36,7 +41,11 @@ def parse_tenant_name(request) # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers # -- all .rb files in that directory are automatically loaded. - + # config.active_storage.variant_processor = :vips + config.middleware.use(ActionDispatch::Cookies) + config.middleware.use(ActionDispatch::Session::CookieStore) + config.action_dispatch.cookies_serializer = :json + config.middleware.use(IPinfoMiddleware, { token: ENV['IPINFO_TOKEN'] || Rails.application.credentials.dig(:ipinfo) }) # Only loads a smaller set of middleware suitable for API only apps. # Middleware like session, flash, cookies can be added back manually. # Skip views, helpers and assets when generating a new resource. diff --git a/config/cable.yml b/config/cable.yml index ed7b5e43..4a3c0b3c 100644 --- a/config/cable.yml +++ b/config/cable.yml @@ -6,5 +6,5 @@ test: production: adapter: redis - url: redis://localhost:6379/1 + url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> channel_prefix: open_tour_api_production diff --git a/config/database.yml b/config/database.yml new file mode 100644 index 00000000..faeb8607 --- /dev/null +++ b/config/database.yml @@ -0,0 +1,22 @@ +default: &default + adapter: <%= ENV['DB_ADAPTER'] || 'postgresql' %> + encoding: utf8 + pool: 50 + schema_search_path: "public,shared_extensions" + username: <%= ENV['DB_USERNAME'] || 'user'%> + host: <%= ENV['DB_HOSTNAME'] || 'localhost' %> + schema_search_path: public, postgis + password: <%= ENV['DB_PASSWORD'] || 'password' %> + database: <%= ENV['DB_NAME'] || 'otb' %> + +development: + <<: *default + database: <%= ENV['DB_NAME'] || 'otb_development' %> + username: <%= ENV['DB_USERNAME'] || 'user' %> + password: <%= ENV['DB_PASSWORD'] || 'password' %> + host: <%= ENV['DB_HOSTNAME'] || 'localhost' %> + +test: + <<: *default + database: <%= ENV['TEST_DB_NAME'] || 'otb_test' %> + \ No newline at end of file diff --git a/config/database.yml.backup b/config/database.yml.backup new file mode 100644 index 00000000..0693ea9e --- /dev/null +++ b/config/database.yml.backup @@ -0,0 +1,45 @@ +default: &default + adapter: <%= ENV['DB_ADAPTER'] || 'postgresql' %> + encoding: utf8 + pool: 50 + schema_search_path: "public,shared_extensions" + username: <%= ENV['DB_USERNAME'] || 'user'%> + host: <%= ENV['DB_HOSTNAME'] || 'localhost' %> + schema_search_path: public, postgis + password: <%= ENV['DB_PASSWORD'] || 'password' %> + database: <%= ENV['DB_NAME'] || 'otb' %> + +# Commented out mysql section that was causing encryption errors +# mysql: &mysql +# <<: *default +# adapter: mysql2 +# database: public +# username: <%= Rails.application.credentials.dig(:dbTest, :mysql, :user) %> +# password: <%= Rails.application.credentials.dig(:dbTest, :mysql, :pw) %> +# host: <%= Rails.application.credentials.dig(:dbTest, :mysql, :host) %> + +development: + <<: *default + database: <%= ENV['DB_NAME'] || 'otb_development' %> + username: <%= ENV['DB_USERNAME'] || 'user' %> + password: <%= ENV['DB_PASSWORD'] || 'password' %> + host: <%= ENV['DB_HOSTNAME'] || 'localhost' %> + +# Commented out staging and production - use environment variables when needed +# staging: +# <<: *default +# database: <%= Rails.application.credentials.dig(:rdsStaging, :db) %> +# username: <%= Rails.application.credentials.dig(:rdsStaging, :user) %> +# password: <%= Rails.application.credentials.dig(:rdsStaging, :pw) %> +# host: <%= Rails.application.credentials.dig(:rdsStaging, :host) %> + +# production: +# <<: *default +# database: <%= Rails.application.credentials.dig(:rdsProduction, :db) %> +# username: <%= Rails.application.credentials.dig(:rdsProduction, :user) %> +# password: <%= Rails.application.credentials.dig(:rdsProduction, :pw) %> +# host: <%= Rails.application.credentials.dig(:rdsProduction, :host) %> + +test: + <<: *default + database: <%= ENV['TEST_DB_NAME'] || 'otb_test' %> \ No newline at end of file diff --git a/config/database.yml.travis b/config/database.yml.travis deleted file mode 100644 index 08b31c12..00000000 --- a/config/database.yml.travis +++ /dev/null @@ -1,13 +0,0 @@ -postgres: &postgres - adapter: postgresql - username: postgres - schema_search_path: "public,shared_extensions" - -mysql: &mysql - adapter: mysql2 - username: root - -test: - database: otb_test - host: localhost - <<: *<%= ENV['DB'] || "postgres" %> diff --git a/config/deploy.rb b/config/deploy.rb index 15f21300..05d670ec 100644 --- a/config/deploy.rb +++ b/config/deploy.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # config valid for current version and patch releases of Capistrano -lock '~> 3.12.1' +lock '~> 3.17.0' set :application, 'otb-api-server' set :repo_url, 'git@github.com:ecds/otb-api-server.git' @@ -23,13 +23,11 @@ # set :pty, true # Default value for :linked_files is [] -append :linked_files, 'config/database.yml', 'config/initializers/auth.rb', 'config/secrets.yml' +append :linked_files, 'config/master.key' +append :linked_dirs, 'public/storage' # append :linked_files, 'config/database.yml' -# Default value for linked_dirs is [] -append :linked_dirs, 'public/uploads' - # Default value for default_env is {} # set :default_env, { path: '/opt/ruby/bin:$PATH' } diff --git a/config/deploy/production.rb b/config/deploy/production.rb index 66025852..906861df 100644 --- a/config/deploy/production.rb +++ b/config/deploy/production.rb @@ -1,62 +1,13 @@ -# server-based syntax -# ====================== -# Defines a single server with a list of roles and multiple properties. -# You can define all roles on a single server, or split them: set :branch, 'develop' -server "opentour.emory.edu", user: "deploy", roles: %w{app db web}, primary: :my_value -# server "example.com", user: "deploy", roles: %w{app web}, other_property: :other_value -# server "db.example.com", user: "deploy", roles: %w{db} +# server '44.192.30.237', user: 'deploy', roles: %w{app db web}, primary: :my_value +role :app, %w{3.86.138.59 3.236.252.227}, user: 'deploy' +role :web, %w{3.86.138.59 3.236.252.227}, user: 'deploy' +role :db, %w{3.86.138.59 3.236.252.227}, user: 'deploy' +set :deploy_to, '/data/otb-api-server' - -# role-based syntax -# ================== - -# Defines a role with one or multiple servers. The primary server in each -# group is considered to be the first unless any hosts have the primary -# property set. Specify the username and a domain or IP for the server. -# Don't use `:all`, it's a meta role. - -# role :app, %w{deploy@example.com}, my_property: :my_value -# role :web, %w{user1@primary.com user2@additional.com}, other_property: :other_value -# role :db, %w{deploy@example.com} - - - -# Configuration -# ============= -# You can set any configuration variable like in config/deploy.rb -# These variables are then only loaded and set in this stage. -# For available Capistrano configuration variables see the documentation page. -# http://capistranorb.com/documentation/getting-started/configuration/ -# Feel free to add new variables to customise your setup. - - - -# Custom SSH Options -# ================== -# You may pass any option but keep in mind that net/ssh understands a -# limited set of options, consult the Net::SSH documentation. -# http://net-ssh.github.io/net-ssh/classes/Net/SSH.html#method-c-start -# -# Global options -# -------------- -# set :ssh_options, { -# keys: %w(/home/rlisowski/.ssh/id_rsa), -# forward_agent: false, -# auth_methods: %w(password) -# } -# -# The server-based syntax can be used to override options: -# ------------------------------------ -# server "example.com", -# user: "user_name", -# roles: %w{web app}, -# ssh_options: { -# user: "user_name", # overrides user setting above -# keys: %w(/home/user_name/.ssh/id_rsa), -# forward_agent: false, -# auth_methods: %w(publickey password) -# # password: "please use keys" -# } +set :ssh_options, { + forward_agent: false, + auth_methods: %w(publickey) +} diff --git a/config/deploy/staging.rb b/config/deploy/staging.rb index 1a72db53..16ba1d28 100644 --- a/config/deploy/staging.rb +++ b/config/deploy/staging.rb @@ -1,13 +1,13 @@ # frozen_string_literal: true -set :branch, 'develop3.0' +set :branch, 'develop' # server-based syntax # ====================== # Defines a single server with a list of roles and multiple properties. # You can define all roles on a single server, or split them: -server "otb.ecdsdev.org", user: "deploy", roles: %w{app db web}, primary: :my_value +server "3.238.239.164", user: "deploy", roles: %w{app db web}, primary: :my_value # server "otb.ecdsdev.org", user: "deploy", roles: %w{app web}, other_property: :other_value # server "db.otb.ecdsdev.org", user: "deploy", roles: %w{db} @@ -21,9 +21,9 @@ # property set. Specify the username and a domain or IP for the server. # Don't use `:all`, it's a meta role. -# role :app, %w{deploy@otb.ecdsdev.org}, my_property: :my_value -# role :web, %w{user1@primary.com user2@additional.com}, other_property: :other_value -# role :db, %w{deploy@otb.ecdsdev.org} +role :app, %w{deploy@3.238.239.164} +role :web, %w{user1@3.238.239.164} +role :db, %w{deploy@3.238.239.164} @@ -45,11 +45,13 @@ # # Global options # -------------- -# set :ssh_options, { -# keys: %w(/home/rlisowski/.ssh/id_rsa), -# forward_agent: false, -# auth_methods: %w(password) -# } + set :ssh_options, { + forward_agent: false, + auth_methods: %w(publickey) + } + + set :branch, 'develop' + set :deploy_to, '/data/otb-api' # # The server-based syntax can be used to override options: # ------------------------------------ diff --git a/config/environment.rb b/config/environment.rb index 0ea7aadb..f9f287fd 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -4,7 +4,7 @@ require_relative 'application' # Initialize the Rails application. -Rails.application.initialize! Rails.application.configure do config.force_ssl = true end +Rails.application.initialize! diff --git a/config/environments/development.rb b/config/environments/development.rb index 12ba559e..747b0597 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -2,7 +2,8 @@ Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. - + config.hosts << 'otb.org' + Rails.application.routes.default_url_options[:host] = 'https://otb.org:3000' # In the development environment your application's code is reloaded on # every request. This slows down response time but is perfect for development # since you don't have to restart the web server when you make code changes. @@ -14,6 +15,9 @@ # Show full error reports. config.consider_all_requests_local = true + # Active Storage + config.active_storage.service = :local + # Enable/disable caching. By default caching is disabled. if Rails.root.join('tmp/caching-dev.txt').exist? config.action_controller.perform_caching = true diff --git a/config/environments/mysql.rb b/config/environments/mysql.rb new file mode 120000 index 00000000..c85ef2a0 --- /dev/null +++ b/config/environments/mysql.rb @@ -0,0 +1 @@ +development.rb \ No newline at end of file diff --git a/config/environments/production.rb b/config/environments/production.rb index 7118d532..98b48202 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -1,8 +1,13 @@ # frozen_string_literal: true Rails.application.configure do + config.hosts << 'api.opentour.site' + Rails.application.routes.default_url_options[:host] = 'https://api.opentour.site' # Settings specified here will take precedence over those in config/application.rb. + # Store uploaded files on the local file system in a temporary directory. + config.active_storage.service = :production + # Code is not reloaded between requests. config.cache_classes = true @@ -82,7 +87,7 @@ # Do not dump schema after migrations. config.active_record.dump_schema_after_migration = false - ENV['BASE_URL'] = 'https://api.opentour.emory.edu' + ENV['BASE_URL'] = 'https://api.opentour.site' ENV['INSECURE_IMAGE_BASE_URL'] = 'http://otbimages.ecdsdev.org' end diff --git a/config/environments/staging.rb b/config/environments/staging.rb index d79dd7cb..45c49784 100644 --- a/config/environments/staging.rb +++ b/config/environments/staging.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true Rails.application.configure do + config.hosts << 'otb-api.ecdsdev.org' + Rails.application.routes.default_url_options[:host] = 'https://otb-api.ecdsdev.org' # Settings specified here will take precedence over those in config/application.rb. # In the development environment your application's code is reloaded on @@ -49,4 +51,6 @@ config.file_watcher = ActiveSupport::FileUpdateChecker ENV['BASE_URL'] = 'https://otb-api.ecdsdev.org' + + config.active_storage.service = :staging end diff --git a/config/environments/test.rb b/config/environments/test.rb index 5971b4bf..668a85ce 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -3,6 +3,9 @@ Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. + # Store uploaded files on the local file system in a temporary directory. + config.active_storage.service = :test + # The test environment is used exclusively to run your application's # test suite. You never need to work with it otherwise. Remember that # your test database is "scratch space" for the test suite and is wiped @@ -44,5 +47,12 @@ # This is needed for the tests to request tests to pass when subdomain is set. config.action_dispatch.tld_length = 0 + config.force_ssl = false + + # config.active_storage.service = :test + # config.consider_all_requests_local = true + # config.action_controller.perform_caching = false + # config.host = 'localhost:3030' + # config.action_controller.default_url_options = { host: 'localhost:3030' } end diff --git a/config/initializers/apartment.rb b/config/initializers/apartment.rb index 1fef55ec..46ac452a 100644 --- a/config/initializers/apartment.rb +++ b/config/initializers/apartment.rb @@ -3,6 +3,6 @@ # require 'directory_elevator' Apartment.configure do |config| config.tenant_names = -> { TourSet.pluck :subdir } - config.excluded_models = ['Login', 'User', 'Role', 'TourSetAdmin', 'TourSet'] + config.excluded_models = ['User', 'Role', 'TourSetAdmin', 'TourSet', 'EcdsRailsAuthEngine::Login', 'EcdsRailsAuthEngine::Token', 'Theme'] config.persistent_schemas = ['shared_extensions'] end diff --git a/config/initializers/application_controller_renderer.rb b/config/initializers/application_controller_renderer.rb new file mode 100644 index 00000000..89d2efab --- /dev/null +++ b/config/initializers/application_controller_renderer.rb @@ -0,0 +1,8 @@ +# Be sure to restart your server when you modify this file. + +# ActiveSupport::Reloader.to_prepare do +# ApplicationController.renderer.defaults.merge!( +# http_host: 'example.org', +# https: false +# ) +# end diff --git a/config/initializers/backtrace_silencers.rb b/config/initializers/backtrace_silencers.rb new file mode 100644 index 00000000..59385cdf --- /dev/null +++ b/config/initializers/backtrace_silencers.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. +# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } + +# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. +# Rails.backtrace_cleaner.remove_silencers! diff --git a/config/initializers/cookie_session.rb b/config/initializers/cookie_session.rb new file mode 100644 index 00000000..53cfc4f4 --- /dev/null +++ b/config/initializers/cookie_session.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true +Rails.application.config.session_store(:cookie_store, key: '_otb_session') +Rails.application.config.action_dispatch.cookies_serializer = :json diff --git a/config/initializers/cors.rb b/config/initializers/cors.rb index eec10024..f341144d 100644 --- a/config/initializers/cors.rb +++ b/config/initializers/cors.rb @@ -9,10 +9,11 @@ Rails.application.config.middleware.insert_before 0, Rack::Cors do allow do - origins '*' + origins 'https://lvh.me:4200', 'https://otb.ecdsdev.org', 'https://opentour.site', /.*\.opentour.site/, /.*\.lvh.me:4200/, /.*localhost:3000/, /.*\.urbanspatialhistory.org/ resource '*', headers: :any, - methods: [:get, :post, :put, :patch, :delete, :options, :head] + methods: [:get, :post, :put, :patch, :delete, :options, :head], + credentials: true end end diff --git a/config/initializers/disable_ssl_in_development.rb b/config/initializers/disable_ssl_in_development.rb new file mode 100644 index 00000000..13c334c7 --- /dev/null +++ b/config/initializers/disable_ssl_in_development.rb @@ -0,0 +1 @@ +Rails.application.configure { config.force_ssl = false } if Rails.env.development? diff --git a/config/initializers/filter_parameter_logging.rb b/config/initializers/filter_parameter_logging.rb index 7a4f47b4..a18206dc 100644 --- a/config/initializers/filter_parameter_logging.rb +++ b/config/initializers/filter_parameter_logging.rb @@ -3,4 +3,4 @@ # Be sure to restart your server when you modify this file. # Configure sensitive parameters which will be filtered from the log file. -Rails.application.config.filter_parameters += [:password] +Rails.application.config.filter_parameters += [:password, :base_sixty_four] diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb index aa7435fb..ac033bf9 100644 --- a/config/initializers/inflections.rb +++ b/config/initializers/inflections.rb @@ -1,4 +1,3 @@ -# frozen_string_literal: true # Be sure to restart your server when you modify this file. # Add new inflection rules using the following format. Inflections diff --git a/config/initializers/kaminari_config.rb b/config/initializers/kaminari_config.rb new file mode 100644 index 00000000..4ba6ee3e --- /dev/null +++ b/config/initializers/kaminari_config.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +Kaminari.configure do |config| + # config.default_per_page = 25 + # config.max_per_page = nil + # config.window = 4 + # config.outer_window = 0 + # config.left = 0 + # config.right = 0 + # config.page_method_name = :page + # config.param_name = :page + # config.max_pages = nil + # config.params_on_first_page = false +end diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb new file mode 100644 index 00000000..dc189968 --- /dev/null +++ b/config/initializers/mime_types.rb @@ -0,0 +1,4 @@ +# Be sure to restart your server when you modify this file. + +# Add new mime types for use in respond_to blocks: +# Mime::Type.register "text/richtext", :rtf diff --git a/config/initializers/new_framework_defaults_5_2.rb b/config/initializers/new_framework_defaults_5_2.rb new file mode 100644 index 00000000..c383d072 --- /dev/null +++ b/config/initializers/new_framework_defaults_5_2.rb @@ -0,0 +1,38 @@ +# Be sure to restart your server when you modify this file. +# +# This file contains migration options to ease your Rails 5.2 upgrade. +# +# Once upgraded flip defaults one by one to migrate to the new default. +# +# Read the Guide for Upgrading Ruby on Rails for more info on each option. + +# Make Active Record use stable #cache_key alongside new #cache_version method. +# This is needed for recyclable cache keys. +# Rails.application.config.active_record.cache_versioning = true + +# Use AES-256-GCM authenticated encryption for encrypted cookies. +# Also, embed cookie expiry in signed or encrypted cookies for increased security. +# +# This option is not backwards compatible with earlier Rails versions. +# It's best enabled when your entire app is migrated and stable on 5.2. +# +# Existing cookies will be converted on read then written with the new scheme. +# Rails.application.config.action_dispatch.use_authenticated_cookie_encryption = true + +# Use AES-256-GCM authenticated encryption as default cipher for encrypting messages +# instead of AES-256-CBC, when use_authenticated_message_encryption is set to true. +# Rails.application.config.active_support.use_authenticated_message_encryption = true + +# Add default protection from forgery to ActionController::Base instead of in +# ApplicationController. +# Rails.application.config.action_controller.default_protect_from_forgery = true + +# Store boolean values are in sqlite3 databases as 1 and 0 instead of 't' and +# 'f' after migrating old data. +# Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = true + +# Use SHA-1 instead of MD5 to generate non-sensitive digests, such as the ETag header. +# Rails.application.config.active_support.use_sha1_digests = true + +# Make `form_with` generate id attributes for any generated HTML tags. +# Rails.application.config.action_view.form_with_generates_ids = true diff --git a/config/initializers/pagy.rb b/config/initializers/pagy.rb new file mode 100644 index 00000000..6149760f --- /dev/null +++ b/config/initializers/pagy.rb @@ -0,0 +1,3 @@ +# require 'pagy/extras/headers' +# Pagy::DEFAULT[:items] = 2 +# Pagy::DEFAULT.freeze diff --git a/config/initializers/s3.rb b/config/initializers/s3.rb new file mode 100644 index 00000000..e3e8b945 --- /dev/null +++ b/config/initializers/s3.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +# Aws.config.update({ +# credentials: Aws::Credentials.new( +# Rails.application.credentials.s3Staging[:access_key_id], +# Rails.application.credentials.s3Staging[:secret_access_key] +# ) +# }) + +# Sometimes the service URL expires too quickly. +Rails.application.config.active_storage.service_urls_expire_in = 1.week diff --git a/config/initializers/string.rb b/config/initializers/string.rb new file mode 100644 index 00000000..d57e10a7 --- /dev/null +++ b/config/initializers/string.rb @@ -0,0 +1,32 @@ +require "active_support/inflector" + +class String + def parameterize_intl(separator: '-', preserve_case: false, locale: nil) + # Replace accented chars with their ASCII equivalents. + transliterated_string = ActiveSupport::Inflector.transliterate(self, replacement = '~', locale: locale) + + parameterized_string = if transliterated_string.include?('~') + self.gsub(/[!@#$%^&*()-=_+|;':",.<>?\s']/, separator) + else + transliterated_string.gsub(/[^a-z0-9\-_]+/i, separator) + end + + unless separator.nil? || separator.empty? + if separator == '_'.freeze + re_duplicate_separator = /-{2,}/ + re_leading_trailing_separator = /^-|-$/ + else + re_sep = Regexp.escape(separator) + re_duplicate_separator = /#{re_sep}{2,}/ + re_leading_trailing_separator = /^#{re_sep}|#{re_sep}$/ + end + # No more than one of the separator in a row. + parameterized_string.gsub!(re_duplicate_separator, separator) + # Remove leading/trailing separator. + parameterized_string.gsub!(re_leading_trailing_separator, ''.freeze) + end + + parameterized_string.downcase! unless preserve_case + parameterized_string + end +end diff --git a/config/initializers/version.rb b/config/initializers/version.rb index 912d5d2d..a0e6e767 100644 --- a/config/initializers/version.rb +++ b/config/initializers/version.rb @@ -1,5 +1,5 @@ module OpenTourApi class Application - VERSION = '3.0.1rc' + VERSION = '3.0' end end \ No newline at end of file diff --git a/config/initializers/wrap_parameters.rb b/config/initializers/wrap_parameters.rb index e7148a36..bbfc3961 100644 --- a/config/initializers/wrap_parameters.rb +++ b/config/initializers/wrap_parameters.rb @@ -1,5 +1,3 @@ -# frozen_string_literal: true - # Be sure to restart your server when you modify this file. # This file contains settings for ActionController::ParamsWrapper which @@ -9,3 +7,8 @@ ActiveSupport.on_load(:action_controller) do wrap_parameters format: [:json] end + +# To enable root element in JSON for ActiveRecord objects. +# ActiveSupport.on_load(:active_record) do +# self.include_root_in_json = true +# end diff --git a/config/puma.rb b/config/puma.rb index 6ec461f5..b2102072 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -1,21 +1,22 @@ -# frozen_string_literal: true - # Puma can serve each request in a thread from an internal thread pool. # The `threads` method setting takes two numbers: a minimum and maximum. # Any libraries that use thread pools should be configured to match # the maximum value specified for Puma. Default is set to 5 threads for minimum # and maximum; this matches the default thread size of Active Record. # -threads_count = ENV.fetch('RAILS_MAX_THREADS') { 5 } +threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } threads threads_count, threads_count # Specifies the `port` that Puma will listen on to receive requests; default is 3000. # -port ENV.fetch('PORT') { 3000 } +port ENV.fetch("PORT") { 3000 } # Specifies the `environment` that Puma will run in. # -environment ENV.fetch('RAILS_ENV') { 'development' } +environment ENV.fetch("RAILS_ENV") { "development" } + +# Specifies the `pidfile` that Puma will use. +pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" } # Specifies the number of `workers` to boot in clustered mode. # Workers are forked webserver processes. If using threads and workers together @@ -28,31 +29,9 @@ # Use the `preload_app!` method when specifying a `workers` number. # This directive tells Puma to first boot the application and load code # before forking the application. This takes advantage of Copy On Write -# process behavior so workers use less memory. If you use this option -# you need to make sure to reconnect any threads in the `on_worker_boot` -# block. +# process behavior so workers use less memory. # # preload_app! -# If you are preloading your application and using Active Record, it's -# recommended that you close any connections to the database before workers -# are forked to prevent connection leakage. -# -# before_fork do -# ActiveRecord::Base.connection_pool.disconnect! if defined?(ActiveRecord) -# end - -# The code in the `on_worker_boot` will be called if you are using -# clustered mode by specifying a number of `workers`. After each worker -# process is booted, this block will be run. If you are using the `preload_app!` -# option, you will want to use this block to reconnect to any threads -# or connections that may have been created at application boot, as Ruby -# cannot share connections between processes. -# -# on_worker_boot do -# ActiveRecord::Base.establish_connection if defined?(ActiveRecord) -# end -# - # Allow puma to be restarted by `rails restart` command. plugin :tmp_restart diff --git a/config/routes.rb b/config/routes.rb index aaa110c3..8fbfacdf 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -13,28 +13,29 @@ resources :tour_set_admins scope ':tenant' do - scope module: :v1, constraints: ApiVersion.new('v1') do - resources :tours, only: :index - end scope module: :v3, constraints: ApiVersion.new('v3', true) do + resources :tour_authors, path: 'tour-authors' resources :users - resources :modes, only: [:index] + resources :modes, only: [:index, :show] resources :tour_sets, path: 'tour-sets' resources :tour_set_admins, path: 'tour-set-users' resources :tour_collections, path: 'tour-collections' resources :tour_media, path: 'tour-media' + resources :map_overlays, path: 'map-overlays' + resources :map_icons, path: 'map-icons' resources :themes resources :tours resources :media resources :stops resources :stop_media, path: 'stop-media' resources :tour_media, path: 'tour-media' + resources :tour_modes, path: 'tour-modes' resources :tour_stops, path: 'tour-stops' resources :flat_pages, path: 'flat-pages' resources :tour_flat_pages, path: 'tour-flat-pages' resources :geojson_tours + end - post '/token', to: 'oauth2#create' - post '/revoke', to: 'oauth2#destroy' end + mount EcdsRailsAuthEngine::Engine, at: '/auth' end diff --git a/config/spring.rb b/config/spring.rb index ff5ba06b..9fa7863f 100644 --- a/config/spring.rb +++ b/config/spring.rb @@ -1,8 +1,6 @@ -# frozen_string_literal: true - -%w( +%w[ .ruby-version .rbenv-vars tmp/restart.txt tmp/caching-dev.txt -).each { |path| Spring.watch(path) } +].each { |path| Spring.watch(path) } diff --git a/config/storage.yml b/config/storage.yml new file mode 100644 index 00000000..0153a1c8 --- /dev/null +++ b/config/storage.yml @@ -0,0 +1,51 @@ +test: + service: Disk + root: <%= Rails.root.join("tmp/storage") %> + +local: + service: S3 + access_key_id: <%= Rails.application.credentials.dig(:s3Staging, :access_key_id) %> + secret_access_key: <%= Rails.application.credentials.dig(:s3Staging, :secret_access_key) %> + region: us-east-1 + bucket: otb-dev + +staging: + service: S3 + access_key_id: <%= Rails.application.credentials.dig(:s3Staging, :access_key_id) %> + secret_access_key: <%= Rails.application.credentials.dig(:s3Staging, :secret_access_key) %> + region: us-east-1 + bucket: opentour + +production: + service: S3 + access_key_id: <%= Rails.application.credentials.dig(:s3Staging, :access_key_id) %> + secret_access_key: <%= Rails.application.credentials.dig(:s3Staging, :secret_access_key) %> + region: us-east-1 + bucket: opentour-prod + +# Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) +# amazon: +# service: S3 +# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> +# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> +# region: us-east-1 +# bucket: your_own_bucket + +# Remember not to checkin your GCS keyfile to a repository +# google: +# service: GCS +# project: your_project +# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> +# bucket: your_own_bucket + +# Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) +# microsoft: +# service: AzureStorage +# storage_account_name: your_account_name +# storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> +# container: your_container_name + +# mirror: +# service: Mirror +# primary: local +# mirrors: [ amazon, google, microsoft ] diff --git a/db/migrate/20200213152142_rename_metadescription.rb b/db/migrate/20200213152142_rename_metadescription.rb index e12ceab6..b9d3725d 100644 --- a/db/migrate/20200213152142_rename_metadescription.rb +++ b/db/migrate/20200213152142_rename_metadescription.rb @@ -1,5 +1,9 @@ class RenameMetadescription < ActiveRecord::Migration[5.2] def change - rename_column :stops, :metadescription, :meta_description + begin + rename_column :stops, :metadescription, :meta_description + rescue + # It's fine + end end end diff --git a/db/migrate/20210518143822_add_email.rb b/db/migrate/20210518143822_add_email.rb new file mode 100644 index 00000000..fcc835e7 --- /dev/null +++ b/db/migrate/20210518143822_add_email.rb @@ -0,0 +1,5 @@ +class AddEmail < ActiveRecord::Migration[5.2] + def change + add_column :users, :email, :string + end +end diff --git a/db/migrate/20210602201922_create_active_storage_tables.active_storage.rb b/db/migrate/20210602201922_create_active_storage_tables.active_storage.rb new file mode 100644 index 00000000..0b2ce257 --- /dev/null +++ b/db/migrate/20210602201922_create_active_storage_tables.active_storage.rb @@ -0,0 +1,27 @@ +# This migration comes from active_storage (originally 20170806125915) +class CreateActiveStorageTables < ActiveRecord::Migration[5.2] + def change + create_table :active_storage_blobs do |t| + t.string :key, null: false + t.string :filename, null: false + t.string :content_type + t.text :metadata + t.bigint :byte_size, null: false + t.string :checksum, null: false + t.datetime :created_at, null: false + + t.index [ :key ], unique: true + end + + create_table :active_storage_attachments do |t| + t.string :name, null: false + t.references :record, null: false, polymorphic: true, index: false + t.references :blob, null: false + + t.datetime :created_at, null: false + + t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true + t.foreign_key :active_storage_blobs, column: :blob_id + end + end +end diff --git a/db/migrate/20210604131202_add_base64.rb b/db/migrate/20210604131202_add_base64.rb new file mode 100644 index 00000000..11a7dcda --- /dev/null +++ b/db/migrate/20210604131202_add_base64.rb @@ -0,0 +1,5 @@ +class AddBase64 < ActiveRecord::Migration[6.0] + def change + add_column :media, :base64, :text + end +end diff --git a/db/migrate/20210604132602_add_video_provider.rb b/db/migrate/20210604132602_add_video_provider.rb new file mode 100644 index 00000000..1c408042 --- /dev/null +++ b/db/migrate/20210604132602_add_video_provider.rb @@ -0,0 +1,5 @@ +class AddVideoProvider < ActiveRecord::Migration[6.0] + def change + add_column :media, :video_provider, :integer, default: 0 + end +end diff --git a/db/migrate/20210604145226_change_base64.rb b/db/migrate/20210604145226_change_base64.rb new file mode 100644 index 00000000..80142312 --- /dev/null +++ b/db/migrate/20210604145226_change_base64.rb @@ -0,0 +1,5 @@ +class ChangeBase64 < ActiveRecord::Migration[6.0] + def change + rename_column :media, :base64, :base_sixty_four + end +end diff --git a/db/migrate/20210607125915_add_parking_address.rb b/db/migrate/20210607125915_add_parking_address.rb new file mode 100644 index 00000000..fd12fa45 --- /dev/null +++ b/db/migrate/20210607125915_add_parking_address.rb @@ -0,0 +1,5 @@ +class AddParkingAddress < ActiveRecord::Migration[6.0] + def change + add_column :stops, :parking_address, :string + end +end diff --git a/db/migrate/20210607165827_rename_content.rb b/db/migrate/20210607165827_rename_content.rb new file mode 100644 index 00000000..054747b2 --- /dev/null +++ b/db/migrate/20210607165827_rename_content.rb @@ -0,0 +1,5 @@ +class RenameContent < ActiveRecord::Migration[6.0] + def change + rename_column :flat_pages, :content, :body + end +end diff --git a/db/migrate/20210607221303_add_tour_to_stop_slugs.rb b/db/migrate/20210607221303_add_tour_to_stop_slugs.rb new file mode 100644 index 00000000..25244cb0 --- /dev/null +++ b/db/migrate/20210607221303_add_tour_to_stop_slugs.rb @@ -0,0 +1,5 @@ +class AddTourToStopSlugs < ActiveRecord::Migration[6.0] + def change + add_reference :stop_slugs, :tour, foreign_key: true + end +end diff --git a/db/migrate/20210608211705_create_map_overlays.rb b/db/migrate/20210608211705_create_map_overlays.rb new file mode 100644 index 00000000..c4ba6439 --- /dev/null +++ b/db/migrate/20210608211705_create_map_overlays.rb @@ -0,0 +1,14 @@ +class CreateMapOverlays < ActiveRecord::Migration[6.0] + def change + create_table :map_overlays do |t| + t.decimal "south", precision: 100, scale: 8 + t.decimal "north", precision: 100, scale: 8 + t.decimal "east", precision: 100, scale: 8 + t.decimal "west", precision: 100, scale: 8 + t.references :tour, null: true + t.references :stop, null: true + + t.timestamps + end + end +end diff --git a/db/migrate/20210609141823_add_base64_overlay.rb b/db/migrate/20210609141823_add_base64_overlay.rb new file mode 100644 index 00000000..31bb4a60 --- /dev/null +++ b/db/migrate/20210609141823_add_base64_overlay.rb @@ -0,0 +1,5 @@ +class AddBase64Overlay < ActiveRecord::Migration[6.0] + def change + add_column :map_overlays, :base_sixty_four, :text + end +end diff --git a/db/migrate/20210609142132_add_title_overlay.rb b/db/migrate/20210609142132_add_title_overlay.rb new file mode 100644 index 00000000..c531694d --- /dev/null +++ b/db/migrate/20210609142132_add_title_overlay.rb @@ -0,0 +1,5 @@ +class AddTitleOverlay < ActiveRecord::Migration[6.0] + def change + add_column :map_overlays, :title, :text + end +end diff --git a/db/migrate/20210610141706_add_enable_directions.rb b/db/migrate/20210610141706_add_enable_directions.rb new file mode 100644 index 00000000..1913657a --- /dev/null +++ b/db/migrate/20210610141706_add_enable_directions.rb @@ -0,0 +1,5 @@ +class AddEnableDirections < ActiveRecord::Migration[6.0] + def change + add_column :tours, :use_directions, :boolean, default: true + end +end diff --git a/db/migrate/20210610152819_add_default_lang.rb b/db/migrate/20210610152819_add_default_lang.rb new file mode 100644 index 00000000..d6532be1 --- /dev/null +++ b/db/migrate/20210610152819_add_default_lang.rb @@ -0,0 +1,5 @@ +class AddDefaultLang < ActiveRecord::Migration[6.0] + def change + add_column :tours, :default_lng, :integer, default: 0 + end +end diff --git a/db/migrate/20210610180023_add_icon_color.rb b/db/migrate/20210610180023_add_icon_color.rb new file mode 100644 index 00000000..652bbd67 --- /dev/null +++ b/db/migrate/20210610180023_add_icon_color.rb @@ -0,0 +1,5 @@ +class AddIconColor < ActiveRecord::Migration[6.0] + def change + add_column :stops, :icon_color, :string, default: '#D32F2F' + end +end diff --git a/db/migrate/20210610182827_create_map_icons.rb b/db/migrate/20210610182827_create_map_icons.rb new file mode 100644 index 00000000..dc200cea --- /dev/null +++ b/db/migrate/20210610182827_create_map_icons.rb @@ -0,0 +1,9 @@ +class CreateMapIcons < ActiveRecord::Migration[6.0] + def change + create_table :map_icons do |t| + t.text :base_sixty_four + + t.timestamps + end + end +end diff --git a/db/migrate/20210610183825_add_title_to_map_icons.rb b/db/migrate/20210610183825_add_title_to_map_icons.rb new file mode 100644 index 00000000..6aac4c6d --- /dev/null +++ b/db/migrate/20210610183825_add_title_to_map_icons.rb @@ -0,0 +1,6 @@ +class AddTitleToMapIcons < ActiveRecord::Migration[6.0] + def change + add_column :map_icons, :title, :string + add_reference :stops, :map_icon, null: true, foreign_key: true + end +end diff --git a/db/migrate/20210614140848_add_logo64_to_tour_sets.rb b/db/migrate/20210614140848_add_logo64_to_tour_sets.rb new file mode 100644 index 00000000..486a0d34 --- /dev/null +++ b/db/migrate/20210614140848_add_logo64_to_tour_sets.rb @@ -0,0 +1,5 @@ +class AddLogo64ToTourSets < ActiveRecord::Migration[6.0] + def change + add_column :tour_sets, :base_sixty_four, :text + end +end diff --git a/db/migrate/20210614154357_remove_logo_from_tour_sets.rb b/db/migrate/20210614154357_remove_logo_from_tour_sets.rb new file mode 100644 index 00000000..92e49e5f --- /dev/null +++ b/db/migrate/20210614154357_remove_logo_from_tour_sets.rb @@ -0,0 +1,5 @@ +class RemoveLogoFromTourSets < ActiveRecord::Migration[6.0] + def change + remove_column :tour_sets, :logo + end +end diff --git a/db/migrate/20210614154939_add_logo_title_to_tour_sets.rb b/db/migrate/20210614154939_add_logo_title_to_tour_sets.rb new file mode 100644 index 00000000..d034390b --- /dev/null +++ b/db/migrate/20210614154939_add_logo_title_to_tour_sets.rb @@ -0,0 +1,5 @@ +class AddLogoTitleToTourSets < ActiveRecord::Migration[6.0] + def change + add_column :tour_sets, :logo_title, :string + end +end diff --git a/db/migrate/20210702214539_add_file_urls_to_media.rb b/db/migrate/20210702214539_add_file_urls_to_media.rb new file mode 100644 index 00000000..e645d8c4 --- /dev/null +++ b/db/migrate/20210702214539_add_file_urls_to_media.rb @@ -0,0 +1,7 @@ +class AddFileUrlsToMedia < ActiveRecord::Migration[6.0] + def change + add_column :media, :mobile, :string + add_column :media, :tablet, :string + add_column :media, :desktop, :string + end +end diff --git a/db/migrate/20210706150527_add_filename_to_media.rb b/db/migrate/20210706150527_add_filename_to_media.rb new file mode 100644 index 00000000..125fe154 --- /dev/null +++ b/db/migrate/20210706150527_add_filename_to_media.rb @@ -0,0 +1,5 @@ +class AddFilenameToMedia < ActiveRecord::Migration[6.0] + def change + add_column :media, :filename, :string + end +end diff --git a/db/migrate/20210707161803_change_title_to_filename.rb b/db/migrate/20210707161803_change_title_to_filename.rb new file mode 100644 index 00000000..a2eaa124 --- /dev/null +++ b/db/migrate/20210707161803_change_title_to_filename.rb @@ -0,0 +1,6 @@ +class ChangeTitleToFilename < ActiveRecord::Migration[6.0] + def change + rename_column :map_icons, :title, :filename + rename_column :map_overlays, :title, :filename + end +end diff --git a/db/migrate/20210712234206_add_service_name_to_active_storage_blobs.active_storage.rb b/db/migrate/20210712234206_add_service_name_to_active_storage_blobs.active_storage.rb new file mode 100644 index 00000000..9967a132 --- /dev/null +++ b/db/migrate/20210712234206_add_service_name_to_active_storage_blobs.active_storage.rb @@ -0,0 +1,18 @@ +# This migration comes from active_storage (originally 20190112182829) +class AddServiceNameToActiveStorageBlobs < ActiveRecord::Migration[6.0] + def up + unless column_exists?(:active_storage_blobs, :service_name) + add_column :active_storage_blobs, :service_name, :string + + if configured_service = ActiveStorage::Blob.service.name + ActiveStorage::Blob.unscoped.update_all(service_name: configured_service) + end + + change_column :active_storage_blobs, :service_name, :string, null: false + end + end + + def down + remove_column :active_storage_blobs, :service_name + end +end diff --git a/db/migrate/20210712234207_create_active_storage_variant_records.active_storage.rb b/db/migrate/20210712234207_create_active_storage_variant_records.active_storage.rb new file mode 100644 index 00000000..a2862695 --- /dev/null +++ b/db/migrate/20210712234207_create_active_storage_variant_records.active_storage.rb @@ -0,0 +1,12 @@ +# This migration comes from active_storage (originally 20191206030411) +class CreateActiveStorageVariantRecords < ActiveRecord::Migration[6.0] + def change + create_table :active_storage_variant_records do |t| + t.belongs_to :blob, null: false, index: false + t.string :variation_digest, null: false + + t.index %i[ blob_id variation_digest ], name: "index_active_storage_variant_records_uniqueness", unique: true + t.foreign_key :active_storage_blobs, column: :blob_id + end + end +end diff --git a/db/migrate/20210816124019_add_link_to_tour.rb b/db/migrate/20210816124019_add_link_to_tour.rb new file mode 100644 index 00000000..f1b74eb9 --- /dev/null +++ b/db/migrate/20210816124019_add_link_to_tour.rb @@ -0,0 +1,6 @@ +class AddLinkToTour < ActiveRecord::Migration[6.1] + def change + add_column :tours, :link_address, :string + add_column :tours, :link_text, :string + end +end diff --git a/db/migrate/20210831190245_change_id_types.rb b/db/migrate/20210831190245_change_id_types.rb new file mode 100644 index 00000000..8ad84f5f --- /dev/null +++ b/db/migrate/20210831190245_change_id_types.rb @@ -0,0 +1,6 @@ +class ChangeIdTypes < ActiveRecord::Migration[6.1] + def change + change_column :tours, :mode_id, :bigint + change_column :tours, :splash_image_medium_id, :bigint + end +end diff --git a/db/migrate/20210831200851_change_body_type_for_flat_pages.rb b/db/migrate/20210831200851_change_body_type_for_flat_pages.rb new file mode 100644 index 00000000..d26feaa4 --- /dev/null +++ b/db/migrate/20210831200851_change_body_type_for_flat_pages.rb @@ -0,0 +1,5 @@ +class ChangeBodyTypeForFlatPages < ActiveRecord::Migration[6.1] + def change + change_column :flat_pages, :body, :text + end +end diff --git a/db/migrate/20210831202533_drop_tag_tables.rb b/db/migrate/20210831202533_drop_tag_tables.rb new file mode 100644 index 00000000..7ab5a076 --- /dev/null +++ b/db/migrate/20210831202533_drop_tag_tables.rb @@ -0,0 +1,7 @@ +class DropTagTables < ActiveRecord::Migration[6.1] + def change + drop_table :tour_tags + drop_table :stop_tags + drop_table :tags + end +end diff --git a/db/migrate/20210902145305_add_widths_to_medium.rb b/db/migrate/20210902145305_add_widths_to_medium.rb new file mode 100644 index 00000000..f37112ea --- /dev/null +++ b/db/migrate/20210902145305_add_widths_to_medium.rb @@ -0,0 +1,5 @@ +class AddWidthsToMedium < ActiveRecord::Migration[6.1] + def change + add_column :media, :lqip_width, :integer + end +end diff --git a/db/migrate/20210902164843_fix_slug_id.rb b/db/migrate/20210902164843_fix_slug_id.rb new file mode 100644 index 00000000..8e117d92 --- /dev/null +++ b/db/migrate/20210902164843_fix_slug_id.rb @@ -0,0 +1,6 @@ +class FixSlugId < ActiveRecord::Migration[6.1] + def change + remove_column :slugs, :id + add_column :slugs, :id, :primary_key + end +end diff --git a/db/migrate/20210916170901_change_lat_lng_type.rb b/db/migrate/20210916170901_change_lat_lng_type.rb new file mode 100644 index 00000000..2982d935 --- /dev/null +++ b/db/migrate/20210916170901_change_lat_lng_type.rb @@ -0,0 +1,12 @@ +class ChangeLatLngType < ActiveRecord::Migration[6.1] + def change + change_column :map_overlays, :south, :string + change_column :map_overlays, :north, :string + change_column :map_overlays, :east, :string + change_column :map_overlays, :west, :string + change_column :stops, :lat, :string + change_column :stops, :lng, :string + change_column :stops, :parking_lat, :string + change_column :stops, :parking_lng, :string + end +end diff --git a/db/migrate/20211011120714_add_duration.rb b/db/migrate/20211011120714_add_duration.rb new file mode 100644 index 00000000..89f8f8dc --- /dev/null +++ b/db/migrate/20211011120714_add_duration.rb @@ -0,0 +1,6 @@ +class AddDuration < ActiveRecord::Migration[6.1] + def change + add_column :tours, :duration, :integer + add_column :tours, :saved_stop_order, :integer, array: true + end +end diff --git a/db/migrate/20220207133352_add_over_bounds_restrition_to_tour.rb b/db/migrate/20220207133352_add_over_bounds_restrition_to_tour.rb new file mode 100644 index 00000000..3c5d38dc --- /dev/null +++ b/db/migrate/20220207133352_add_over_bounds_restrition_to_tour.rb @@ -0,0 +1,5 @@ +class AddOverBoundsRestritionToTour < ActiveRecord::Migration[6.1] + def change + add_column :tours, :restrict_bounds_to_overlay, :boolean, default: false + end +end diff --git a/db/migrate/20220208172935_add_blank_map_to_tour.rb b/db/migrate/20220208172935_add_blank_map_to_tour.rb new file mode 100644 index 00000000..a3932223 --- /dev/null +++ b/db/migrate/20220208172935_add_blank_map_to_tour.rb @@ -0,0 +1,5 @@ +class AddBlankMapToTour < ActiveRecord::Migration[6.1] + def change + add_column :tours, :blank_map, :boolean, default: false + end +end diff --git a/db/migrate/20220210160507_set_geo_default.rb b/db/migrate/20220210160507_set_geo_default.rb new file mode 100644 index 00000000..6b30c1ed --- /dev/null +++ b/db/migrate/20220210160507_set_geo_default.rb @@ -0,0 +1,5 @@ +class SetGeoDefault < ActiveRecord::Migration[6.1] + def change + change_column :tours, :is_geo, :boolean, default: true + end +end diff --git a/db/migrate/20220211142554_add_restrict_bounds_to_tour.rb b/db/migrate/20220211142554_add_restrict_bounds_to_tour.rb new file mode 100644 index 00000000..2e6eecb9 --- /dev/null +++ b/db/migrate/20220211142554_add_restrict_bounds_to_tour.rb @@ -0,0 +1,5 @@ +class AddRestrictBoundsToTour < ActiveRecord::Migration[6.1] + def change + add_column :tours, :restrict_bounds, :boolean, default: true + end +end diff --git a/db/migrate/20220728131300_add_term_accepted_to_user.rb.rb b/db/migrate/20220728131300_add_term_accepted_to_user.rb.rb new file mode 100644 index 00000000..314a1205 --- /dev/null +++ b/db/migrate/20220728131300_add_term_accepted_to_user.rb.rb @@ -0,0 +1,5 @@ +class AddTermsAcceptedToUsers < ActiveRecord::Migration[6.1] + def change + add_column :users, :terms_accepted, :boolean, default: false + end +end diff --git a/db/schema.rb b/db/schema.rb index df0d600a..9b66af81 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -2,24 +2,68 @@ # of editing this file, please use the migrations feature of Active Record to # incrementally modify your database, and then regenerate this schema definition. # -# Note that this schema.rb definition is the authoritative source for your -# database schema. If you need to create the application database on another -# system, you should be using db:schema:load, not running all the migrations -# from scratch. The latter is a flawed and unsustainable approach (the more migrations -# you'll amass, the slower it'll run and the greater likelihood for issues). +# This file is the source Rails uses to define your schema when running `bin/rails +# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to +# be faster and is potentially less error prone than running all of your +# migrations from scratch. Old migrations may fail to apply correctly if those +# migrations use external dependencies or application code. # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2020_02_13_152142) do +ActiveRecord::Schema.define(version: 2022_08_01_155837) do # These are extensions that must be enabled in order to support this database - # enable_extension "pgcrypto" - # enable_extension "plpgsql" - # enable_extension "uuid-ossp" + enable_extension "pgcrypto" + enable_extension "plpgsql" + enable_extension "uuid-ossp" + + create_table "active_storage_attachments", force: :cascade do |t| + t.string "name", null: false + t.string "record_type", null: false + t.bigint "record_id", null: false + t.bigint "blob_id", null: false + t.datetime "created_at", null: false + t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id" + t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true + end + + create_table "active_storage_blobs", force: :cascade do |t| + t.string "key", null: false + t.string "filename", null: false + t.string "content_type" + t.text "metadata" + t.bigint "byte_size", null: false + t.string "checksum", null: false + t.datetime "created_at", null: false + t.string "service_name", null: false + t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true + end + + create_table "active_storage_variant_records", force: :cascade do |t| + t.bigint "blob_id", null: false + t.string "variation_digest", null: false + t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true + end + + create_table "ecds_rails_auth_engine_logins", force: :cascade do |t| + t.string "who" + t.string "provider" + t.bigint "user_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["user_id"], name: "index_ecds_rails_auth_engine_logins_on_user_id" + end + + create_table "ecds_rails_auth_engine_tokens", force: :cascade do |t| + t.string "token" + t.bigint "login_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end create_table "flat_pages", force: :cascade do |t| t.string "title" - t.text "content" + t.text "body" t.datetime "created_at", null: false t.datetime "updated_at", null: false t.integer "position" @@ -39,6 +83,28 @@ t.index ["user_id"], name: "index_logins_on_user_id" end + create_table "map_icons", force: :cascade do |t| + t.text "base_sixty_four" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.string "filename" + end + + create_table "map_overlays", force: :cascade do |t| + t.string "south" + t.string "north" + t.string "east" + t.string "west" + t.bigint "tour_id" + t.bigint "stop_id" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.text "base_sixty_four" + t.text "filename" + t.index ["stop_id"], name: "index_map_overlays_on_stop_id" + t.index ["tour_id"], name: "index_map_overlays_on_tour_id" + end + create_table "media", force: :cascade do |t| t.string "title" t.text "caption" @@ -54,6 +120,13 @@ t.integer "tablet_height" t.integer "mobile_width" t.integer "mobile_height" + t.text "base_sixty_four" + t.integer "video_provider", default: 0 + t.string "mobile" + t.string "tablet" + t.string "desktop" + t.string "filename" + t.integer "lqip_width" end create_table "modes", force: :cascade do |t| @@ -90,13 +163,9 @@ t.bigint "stop_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.bigint "tour_id" t.index ["stop_id"], name: "index_stop_slugs_on_stop_id" - end - - create_table "stop_tags", force: :cascade do |t| - t.string "title" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.index ["tour_id"], name: "index_stop_slugs_on_tour_id" end create_table "stops", force: :cascade do |t| @@ -106,16 +175,20 @@ t.string "article_link" t.string "video_embed" t.string "video_poster" - t.decimal "lat", precision: 65, scale: 8 - t.decimal "lng", precision: 65, scale: 8 - t.decimal "parking_lat", precision: 65, scale: 8 - t.decimal "parking_lng", precision: 65, scale: 8 + t.string "lat" + t.string "lng" + t.string "parking_lat" + t.string "parking_lng" t.text "direction_intro" t.text "direction_notes" t.datetime "created_at", null: false t.datetime "updated_at", null: false t.string "address" t.bigint "medium_id" + t.string "parking_address" + t.string "icon_color", default: "#D32F2F" + t.bigint "map_icon_id" + t.index ["map_icon_id"], name: "index_stops_on_map_icon_id" t.index ["medium_id"], name: "index_stops_on_medium_id" end @@ -138,12 +211,6 @@ t.index ["tagger_id"], name: "index_taggings_on_tagger_id" end - create_table "tags", id: :serial, force: :cascade do |t| - t.string "name" - t.integer "taggings_count", default: 0 - t.index ["name"], name: "index_tags_on_name", unique: true - end - create_table "themes", force: :cascade do |t| t.string "title" t.datetime "created_at", null: false @@ -210,8 +277,9 @@ t.bigint "tour_id" t.string "external_url" t.text "notes" - t.string "logo" t.string "footer_logo" + t.text "base_sixty_four" + t.string "logo_title" t.index ["tour_id"], name: "index_tour_sets_on_tours_id" end @@ -225,20 +293,12 @@ t.index ["tour_id"], name: "index_tour_stops_on_tour_id" end - create_table "tour_tags", force: :cascade do |t| - t.string "title" - t.decimal "lat", precision: 65, scale: 8 - t.decimal "lng", precision: 65, scale: 8 - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - end - create_table "tours", force: :cascade do |t| t.string "title" t.text "description" t.text "article_link" t.text "google_analytics" - t.boolean "is_geo" + t.boolean "is_geo", default: true t.boolean "published" t.bigint "theme_id" t.datetime "created_at", null: false @@ -249,6 +309,15 @@ t.string "meta_description" t.bigint "medium_id" t.string "map_type" + t.boolean "use_directions", default: true + t.integer "default_lng", default: 0 + t.string "link_address" + t.string "link_text" + t.integer "duration" + t.integer "saved_stop_order", array: true + t.boolean "restrict_bounds_to_overlay", default: false + t.boolean "blank_map", default: false + t.boolean "restrict_bounds", default: true t.index ["medium_id"], name: "index_tours_on_medium_id" t.index ["mode_id"], name: "index_tours_on_mode_id" t.index ["theme_id"], name: "index_tours_on_theme_id" @@ -260,9 +329,15 @@ t.datetime "created_at", null: false t.datetime "updated_at", null: false t.boolean "super", default: false + t.string "email" + t.boolean "terms_accepted", default: false t.index ["login_id"], name: "index_users_on_login_id", unique: true end + add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" + add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id" + add_foreign_key "stop_slugs", "tours" + add_foreign_key "stops", "map_icons" add_foreign_key "stops", "media" add_foreign_key "tour_set_admins", "roles" add_foreign_key "tour_sets", "tours" diff --git a/db/seeds.rb b/db/seeds.rb index 55e857ab..ac21c9ef 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -47,8 +47,8 @@ 4.times do # User.create(display_name: Faker::Movies::Lebowski) - FactoryBot.create(:login) - Login.last.user.update_attribute(:display_name, Faker::Movies::Lebowski) + FactoryBot.create(:user) + # Login.last.user.update_attribute(:display_name, Faker::Movies::Lebowski) end TourSet.all.each do |ts| @@ -73,6 +73,7 @@ end User.all.each do |u| + FactoryBot.create(:login, who: u.email, user_id: u.id, provider: 'earth') next if u.super next if u.tours.present? # u.tours = Tour.all.order(Arel.sql('random()')).limit(Random.new.rand(2..3)) diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 00000000..71261527 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,4 @@ + +/usr/local/bin/bundle exec rake db:migrate + +/usr/local/bin/bundle exec rails server -b 0.0.0.0 \ No newline at end of file diff --git a/lib/snippets.rb b/lib/snippets.rb new file mode 100644 index 00000000..4cef4160 --- /dev/null +++ b/lib/snippets.rb @@ -0,0 +1,273 @@ +sites = TourSet.all.map(&:subdir) + +sites.each do |ts| + Apartment::Tenant.switch! ts + reload! + ids = Medium.all.map(&:id) + ids.each do |id| + # Apartment::Tenant.switch! ts + m = Medium.find(id) + next if m.video.nil? + case m.provider + when 'youtube' + m.update(embed: "//www.youtube.com/embed/#{m.video}", provider: 'youtube') + when 'vimeo' + m.update(embed: "//player.vimeo.com/video/#{m.video}", video_provider: 'vimeo') + end + end +end + +TourSet.all.each do |ts| + puts ts.subdir + Apartment::Tenant.switch! ts.subdir + Medium.all.each do |medium| + if !medium.file.attached? + # medium.delete + puts "#{medium.id} stops: #{medium.stops.count} tours: #{medium.tours.count}" + end + end +end + +Medium.all.each do |m| + if !m.file.attached? and (!m.tours.empty? or !m.stops.empty?) + print m.id + end +end + +# Medium.all.each do |m| + # if File.exist?(m.original_image.path) && !m.file.attached? + # m.file.attach(io: File.open(m.original_image.path), filename: m.original_image.path.split('/').last) +# else +# m.delete +# end +# end + +sites = TourSet.all.map(&:subdir) + +sites.each do |ts| + Apartment::Tenant.switch! ts + reload! + ids = Medium.all.map(&:id) + ids.each do |id| + Apartment::Tenant.switch! ts + m = Medium.find(id) + next if m.file.attached? + if m.original_image.path && File.exist?(m.original_image.path) + m.file.attach( + io: File.open(m.original_image.path), + filename: m.original_image.path.split('/').last, + content_type: m.original_image.content_type + ) + end + end +end + +# sites = TourSet.all.map(&:subdir) +# sites.each do |ts| +# Apartment::Tenant.switch! ts +# reload! +# ids = Medium.all.map(&:id) +# ids.each do |id| +# m = Medium.find(id) +# next if m.file.attached? +# if m.original_image.path && File.exist?(m.original_image.path) +# m.file.attach( +# io: File.open(m.original_image.path), +# filename: m.original_image.path.split('/').last, +# content_type: m.original_image.content_type +# ) +# end +# end +# end + +# IMPORTANT!!!!!! comment out the hooks on the medium model +require 'open-uri' +sites = TourSet.all.map(&:subdir) +sites.each do |ts| + Apartment::Tenant.switch! ts + ids = Medium.all.map(&:id) + ids.each do |id| + m = Medium.find(id) + next if m.original_image.nil? + next if m.file.attached? + begin + puts "https://api.opentour.emory.edu/uploads/#{ts}/#{m.original_image}" + m.file.purge + m.file.attach( + io: URI.open("https://api.opentour.emory.edu/uploads/#{ts}/#{m.original_image}"), + filename: m.original_image + ) + m.filename = m.original_image + m.save! + puts "https://api.opentour.emory.edu/#{ts}/#{m.original_image}" + rescue OpenURI::HTTPError, URI::InvalidURIError + puts 'dang' + end + end +end + +media = [{"id":1,"url":"https://api.opentour.emory.edu/uploads/middle-passage-markers/16315528664.png"},{"id":2,"url":"https://api.opentour.emory.edu/uploads/middle-passage-markers/315299464.jpg"},{"id":4,"url":"https://api.opentour.emory.edu/uploads/middle-passage-markers/16315585158.jpeg"},{"id":11,"url":"https://api.opentour.emory.edu/uploads/middle-passage-markers/16365689809.jpeg"},{"id":8,"url":"https://api.opentour.emory.edu/uploads/middle-passage-markers/16353452580.png"},{"id":9,"url":"https://api.opentour.emory.edu/uploads/middle-passage-markers/16353452967.png"},{"id":15,"url":"https://api.opentour.emory.edu/uploads/middle-passage-markers/16365704670.jpeg"},{"id":19,"url":"https://api.opentour.emory.edu/uploads/middle-passage-markers/16365748166.jpeg"},{"id":16,"url":"https://api.opentour.emory.edu/uploads/middle-passage-markers/16365706674.jpeg"},{"id":17,"url":"https://api.opentour.emory.edu/uploads/middle-passage-markers/16365708608.jpeg"},{"id":3,"url":"https://api.opentour.emory.edu/uploads/middle-passage-markers/16315583885.jpeg"},{"id":5,"url":"https://api.opentour.emory.edu/uploads/middle-passage-markers/16315587373.png"},{"id":6,"url":"https://api.opentour.emory.edu/uploads/middle-passage-markers/16341496140.jpeg"},{"id":14,"url":"https://api.opentour.emory.edu/uploads/middle-passage-markers/16365694188.png"},{"id":10,"url":"https://api.opentour.emory.edu/uploads/middle-passage-markers/16353454287.jpeg"},{"id":7,"url":"https://api.opentour.emory.edu/uploads/middle-passage-markers/16341498295.png"},{"id":18,"url":"https://api.opentour.emory.edu/uploads/middle-passage-markers/16365709114.jpeg"},{"id":12,"url":"https://api.opentour.emory.edu/uploads/middle-passage-markers/16365693051.jpeg"},{"id":13,"url":"https://api.opentour.emory.edu/uploads/middle-passage-markers/16365693188.jpeg"},{"id":22,"url":"https://api.opentour.emory.edu/uploads/middle-passage-markers/671271790.jpg"},{"id":21,"url":"https://api.opentour.emory.edu/uploads/middle-passage-markers/671271790.jpg"},{"id":23,"url":"https://api.opentour.emory.edu/uploads/middle-passage-markers/16442803933.png"},{"id":20,"url":"https://api.opentour.emory.edu/uploads/middle-passage-markers/16365785848.jpeg"}] +ids = media.map {|m| m[:id]} +ids = Medium.all.map(&:id) +ids.each do |id| + Apartment::Tenant.switch! 'battle-of-atlanta' + m = Medium.find(id) + m.file.purge + # next if m.file.attached? + # if m.original_image.path && File.exist?(m.original_image.path) + puts "https://api.opentour.emory.edu#{m.original_image.url}" + m.file.attach( + io: URI.open("https://api.opentour.emory.edu#{m.original_image.url}"), + filename: m.original_image.url.split('/').last, + content_type: m.original_image.content_type + ) + puts m.original_image.url.split('/').last + m.filename = m.original_image.url.split('/').last + m.save! + puts m.file.attached? + # end +end + + +sites = TourSet.all.map(&:subdir) +sites.each do |ts| + Apartment::Tenant.switch! ts + reload! + ids = Medium.all.map(&:id) + ids.each do |id| + Apartment::Tenant.switch! ts + m = Medium.find(id) + m.file.purge if m.file.attached? + m.delete + end + TourMedium.all.each {|tm| tm.delete} + StopMedium.all.each {|tm| tm.delete} +end + +ActiveStorage::Blob.service.send(:path_for, m.file.key) +Apartment::Tenant.switch! 'july-22nd' +ids = Medium.all.map(&:id) +ids.each do |id| + Apartment::Tenant.switch! 'july-22nd' + m = Medium.find(id) + + next if m.file.attached? + + # next unless m.file.attached? + + next unless File.exists? ActiveStorage::Blob.service.send(:path_for, m.file.key) +Apartment::Tenant.switch! 'july-22nd' + m.file.attach( + io: File.open(ActiveStorage::Blob.service.send(:path_for, m.file.key)), + filename: m.file.filename.to_s, + content_type: m.file.content_type + ) + Apartment::Tenant.switch! 'july-22nd' +end + +Apartment::Tenant.switch! 'july-22nd' +ids = MapIcon.all.map(&:id) +ids.each do |id| + Apartment::Tenant.switch! 'july-22nd' + m = MapIcon.find(id) + next unless m.file.attached? + + next unless File.exists? ActiveStorage::Blob.service.send(:path_for, m.file.key) + + m.file.attach( + io: File.open(ActiveStorage::Blob.service.send(:path_for, m.file.key)), + filename: m.file.filename.to_s, + content_type: m.file.content_type + ) +end + +User.all.each do |u| + login = Login.find_by(user_id: u.id) + u.email = login.identification + u.save +end + +sites = TourSet.all.map(&:subdir) + +sites.each do |ts| + puts ts + Apartment::Tenant.switch! ts + Tour.all.each do |t| + puts t.title + if t.bounds + t.is_geo = true + t.save + end + end +end + +sites = TourSet.all.map(&:subdir) + +sites.each do |ts| + puts ts + Apartment::Tenant.switch! ts + Medium.all.each do |m| + begin + next unless m.public_send("#{ts.underscore}_file").present? + next unless m.public_send("#{ts.underscore}_file").attached? + + m.public_send("#{ts.underscore}_file").purge + rescue NoMethodError => error + end + end +end + + +sites = TourSet.all.map(&:subdir) +sites.each do |ts| + Apartment::Tenant.switch! ts + reload! + ids = Medium.all.map(&:id) + ids.each do |id| + m = Medium.find(id) + m.save + end + end +end + +sites = TourSet.all.map(&:subdir) + +sites.each do |ts| + puts ts + Apartment::Tenant.switch! ts + Stop.all.each do |s| + if s.lat + s.update(lat: s.lat.round(5).to_f, lng: s.lng.round(5).to_f) + end + if s.parking_lat + s.update(parking_lat: s.parking_lat.round(5).to_f, parking_lng: s.parking_lng.round(5).to_f) + end + s.save + end +end + +Medium.all.each do |m| + next unless m.desktop_width.nil? + m.save +end + +require 'open-uri' +sites = TourSet.all.map(&:subdir) +sites.each do |ts| + Apartment::Tenant.switch! ts + Medium.all.each do |m| + m.save + m.files.keys.each { |k| URI.open(m.files[k]) } + end +end + +sites = TourSet.all.map(&:subdir) +sites.each do |ts| + Apartment::Tenant.switch! ts + Tour.all.each { |t| t.update(is_geo: true) } +end + +sites.each do |ts| + Apartment::Tenant.switch! ts + Tour.all.each { |t| t.update(restrict_bounds: false) } +end \ No newline at end of file diff --git a/lib/tasks/db_enhancements.rake.bk b/lib/tasks/db_enhancements.rake similarity index 59% rename from lib/tasks/db_enhancements.rake.bk rename to lib/tasks/db_enhancements.rake index 9e7509ae..e3ac5d84 100644 --- a/lib/tasks/db_enhancements.rake.bk +++ b/lib/tasks/db_enhancements.rake @@ -13,14 +13,14 @@ namespace :db do desc 'Also create shared_extensions Schema' task extensions: :environment do - # Create Schema - ActiveRecord::Base.connection.execute 'CREATE SCHEMA IF NOT EXISTS shared_extensions;' - # Enable pgcrypto - ActiveRecord::Base.connection.execute 'CREATE EXTENSION IF NOT EXISTS "pgcrypto" SCHEMA shared_extensions;' - # Enable UUID-OSSP - ActiveRecord::Base.connection.execute 'CREATE EXTENSION IF NOT EXISTS "uuid-ossp" SCHEMA shared_extensions;' - # Grant usage to public - ActiveRecord::Base.connection.execute 'GRANT usage ON SCHEMA shared_extensions to public;' + # # Create Schema + # ActiveRecord::Base.connection.execute 'CREATE SCHEMA IF NOT EXISTS shared_extensions;' + # # Enable pgcrypto + # ActiveRecord::Base.connection.execute 'CREATE EXTENSION IF NOT EXISTS "pgcrypto" SCHEMA shared_extensions;' + # # Enable UUID-OSSP + # ActiveRecord::Base.connection.execute 'CREATE EXTENSION IF NOT EXISTS "uuid-ossp" SCHEMA shared_extensions;' + # # Grant usage to public + # ActiveRecord::Base.connection.execute 'GRANT usage ON SCHEMA shared_extensions to public;' end end diff --git a/lib/vimeo_props.rb b/lib/vimeo_props.rb deleted file mode 100644 index 220b9b40..00000000 --- a/lib/vimeo_props.rb +++ /dev/null @@ -1,19 +0,0 @@ -class VimeoProps - include HTTParty - include Nokogiri - def initialize(video) - @video = video - end - - def id - if @video.include? 'iframe' - Nokogiri::HTML(@video).xpath('//iframe')[0]['src'].split('/')[-1] - else - /\d{9}/.match(@video)[0] - end - end - - def image - HTTParty.get('https://vimeo.com/api/oembed.json?url=https%3A//vimeo.com/#{self.id}')['thumbnail_url'] - end -end diff --git a/lib/youtube_props.rb b/lib/youtube_props.rb deleted file mode 100644 index 2d9526be..00000000 --- a/lib/youtube_props.rb +++ /dev/null @@ -1,16 +0,0 @@ -class YouTubeProps - include YouTubeRails - include Yt - - def initialize(video) - @video = video - end - - def id - YouTubeRails.extract_video_id(@video) - end - - def image - Yt::Video.new(id:id).thumbnail_url('standard') - end -end diff --git a/old_spcs/requests/map_icons_spec.rb b/old_spcs/requests/map_icons_spec.rb new file mode 100644 index 00000000..9acc699b --- /dev/null +++ b/old_spcs/requests/map_icons_spec.rb @@ -0,0 +1,127 @@ +require 'rails_helper' + +# This spec was generated by rspec-rails when you ran the scaffold generator. +# It demonstrates how one might use RSpec to test the controller code that +# was generated by Rails when you ran the scaffold generator. +# +# It assumes that the implementation code is generated by the rails scaffold +# generator. If you are using any extension libraries to generate different +# controller code, this generated spec may or may not pass. +# +# It only uses APIs available in rails and/or rspec-rails. There are a number +# of tools you can use to make these specs even more expressive, but we're +# sticking to rails and rspec-rails APIs to keep things simple and stable. + +RSpec.describe "/map_icons", type: :request do + # This should return the minimal set of attributes required to create a valid + # MapIcon. As you add validations to MapIcon, be sure to + # adjust the attributes here as well. + let(:valid_attributes) { + skip("Add a hash of attributes valid for your model") + } + + let(:invalid_attributes) { + skip("Add a hash of attributes invalid for your model") + } + + # This should return the minimal set of values that should be in the headers + # in order to pass any filters (e.g. authentication) defined in + # MapIconsController, or in your router and rack + # middleware. Be sure to keep this updated too. + let(:valid_headers) { + {} + } + + describe "GET /index" do + it "renders a successful response" do + MapIcon.create! valid_attributes + get map_icons_url, headers: valid_headers, as: :json + expect(response).to be_successful + end + end + + describe "GET /show" do + it "renders a successful response" do + map_icon = MapIcon.create! valid_attributes + get map_icon_url(map_icon), as: :json + expect(response).to be_successful + end + end + + # describe "POST /create" do + # context "with valid parameters" do + # it "creates a new MapIcon" do + # expect { + # post map_icons_url, + # params: { map_icon: valid_attributes }, headers: valid_headers, as: :json + # }.to change(MapIcon, :count).by(1) + # end + + # it "renders a JSON response with the new map_icon" do + # post map_icons_url, + # params: { map_icon: valid_attributes }, headers: valid_headers, as: :json + # expect(response).to have_http_status(:created) + # expect(response.content_type).to match(a_string_including("application/json")) + # end + # end + + # context "with invalid parameters" do + # it "does not create a new MapIcon" do + # expect { + # post map_icons_url, + # params: { map_icon: invalid_attributes }, as: :json + # }.to change(MapIcon, :count).by(0) + # end + + # it "renders a JSON response with errors for the new map_icon" do + # post map_icons_url, + # params: { map_icon: invalid_attributes }, headers: valid_headers, as: :json + # expect(response).to have_http_status(:unprocessable_entity) + # expect(response.content_type).to eq("application/json") + # end + # end + # end + + describe "PATCH /update" do + context "with valid parameters" do + let(:new_attributes) { + skip("Add a hash of attributes valid for your model") + } + + it "updates the requested map_icon" do + map_icon = MapIcon.create! valid_attributes + patch map_icon_url(map_icon), + params: { map_icon: new_attributes }, headers: valid_headers, as: :json + map_icon.reload + skip("Add assertions for updated state") + end + + it "renders a JSON response with the map_icon" do + map_icon = MapIcon.create! valid_attributes + patch map_icon_url(map_icon), + params: { map_icon: new_attributes }, headers: valid_headers, as: :json + expect(response).to have_http_status(:ok) + expect(response.content_type).to match(a_string_including("application/json")) + end + end + + context "with invalid parameters" do + it "renders a JSON response with errors for the map_icon" do + map_icon = MapIcon.create! valid_attributes + patch map_icon_url(map_icon), + params: { map_icon: invalid_attributes }, headers: valid_headers, as: :json + expect(response).to have_http_status(:unprocessable_entity) + expect(response.content_type).to eq("application/json") + end + end + end + + describe "DELETE /destroy" do + it "destroys the requested map_icon" do + map_icon = MapIcon.create! valid_attributes + expect { + delete map_icon_url(map_icon), headers: valid_headers, as: :json + }.to change(MapIcon, :count).by(-1) + end + end +end diff --git a/old_spcs/requests/tour_authors_spec.fix b/old_spcs/requests/tour_authors_spec.fix new file mode 100644 index 00000000..7eb11eef --- /dev/null +++ b/old_spcs/requests/tour_authors_spec.fix @@ -0,0 +1,127 @@ +require 'rails_helper' + +# This spec was generated by rspec-rails when you ran the scaffold generator. +# It demonstrates how one might use RSpec to test the controller code that +# was generated by Rails when you ran the scaffold generator. +# +# It assumes that the implementation code is generated by the rails scaffold +# generator. If you are using any extension libraries to generate different +# controller code, this generated spec may or may not pass. +# +# It only uses APIs available in rails and/or rspec-rails. There are a number +# of tools you can use to make these specs even more expressive, but we're +# sticking to rails and rspec-rails APIs to keep things simple and stable. + +RSpec.describe "/tour_authors", type: :request do + # This should return the minimal set of attributes required to create a valid + # TourAuthor. As you add validations to TourAuthor, be sure to + # adjust the attributes here as well. + let(:valid_attributes) { + skip("Add a hash of attributes valid for your model") + } + + let(:invalid_attributes) { + skip("Add a hash of attributes invalid for your model") + } + + # This should return the minimal set of values that should be in the headers + # in order to pass any filters (e.g. authentication) defined in + # TourAuthorsController, or in your router and rack + # middleware. Be sure to keep this updated too. + let(:valid_headers) { + {} + } + + describe "GET /index" do + it "renders a successful response" do + TourAuthor.create! valid_attributes + get tour_authors_url, headers: valid_headers, as: :json + expect(response).to be_successful + end + end + + describe "GET /show" do + it "renders a successful response" do + tour_author = TourAuthor.create! valid_attributes + get tour_author_url(tour_author), as: :json + expect(response).to be_successful + end + end + + describe "POST /create" do + context "with valid parameters" do + it "creates a new TourAuthor" do + expect { + post tour_authors_url, + params: { tour_author: valid_attributes }, headers: valid_headers, as: :json + }.to change(TourAuthor, :count).by(1) + end + + it "renders a JSON response with the new tour_author" do + post tour_authors_url, + params: { tour_author: valid_attributes }, headers: valid_headers, as: :json + expect(response).to have_http_status(:created) + expect(response.content_type).to match(a_string_including("application/json")) + end + end + + context "with invalid parameters" do + it "does not create a new TourAuthor" do + expect { + post tour_authors_url, + params: { tour_author: invalid_attributes }, as: :json + }.to change(TourAuthor, :count).by(0) + end + + it "renders a JSON response with errors for the new tour_author" do + post tour_authors_url, + params: { tour_author: invalid_attributes }, headers: valid_headers, as: :json + expect(response).to have_http_status(:unprocessable_entity) + expect(response.content_type).to eq("application/json") + end + end + end + + describe "PATCH /update" do + context "with valid parameters" do + let(:new_attributes) { + skip("Add a hash of attributes valid for your model") + } + + it "updates the requested tour_author" do + tour_author = TourAuthor.create! valid_attributes + patch tour_author_url(tour_author), + params: { tour_author: new_attributes }, headers: valid_headers, as: :json + tour_author.reload + skip("Add assertions for updated state") + end + + it "renders a JSON response with the tour_author" do + tour_author = TourAuthor.create! valid_attributes + patch tour_author_url(tour_author), + params: { tour_author: new_attributes }, headers: valid_headers, as: :json + expect(response).to have_http_status(:ok) + expect(response.content_type).to match(a_string_including("application/json")) + end + end + + context "with invalid parameters" do + it "renders a JSON response with errors for the tour_author" do + tour_author = TourAuthor.create! valid_attributes + patch tour_author_url(tour_author), + params: { tour_author: invalid_attributes }, headers: valid_headers, as: :json + expect(response).to have_http_status(:unprocessable_entity) + expect(response.content_type).to eq("application/json") + end + end + end + + describe "DELETE /destroy" do + it "destroys the requested tour_author" do + tour_author = TourAuthor.create! valid_attributes + expect { + delete tour_author_url(tour_author), headers: valid_headers, as: :json + }.to change(TourAuthor, :count).by(-1) + end + end +end diff --git a/spec/requests/v3/abalities_spec.rb.fix b/old_spcs/requests/v3/abalities_spec.rb.fix similarity index 100% rename from spec/requests/v3/abalities_spec.rb.fix rename to old_spcs/requests/v3/abalities_spec.rb.fix diff --git a/old_spcs/requests/v3/flat_pages_spec.rb b/old_spcs/requests/v3/flat_pages_spec.rb new file mode 100644 index 00000000..a7e7b2cf --- /dev/null +++ b/old_spcs/requests/v3/flat_pages_spec.rb @@ -0,0 +1,145 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'ecds_rails_auth_engine' + +RSpec.describe 'V3::FlatPages', type: :request do + let!(:theme) { create(:theme) } + let!(:tours) { create_list(:tour_with_flat_pages, 1, theme: theme) } + let!(:flat_page) { tours.first.flat_pages.first } + let(:tour_id) { tours.first.id } + + context 'create tour with flat pages' do + before { + user = create(:user) + user.update(super: false) + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + user.tours = [] + signed_cookie(user) + cookies['auth'] = EcdsRailsAuthEngine::Login.find_by(user_id: user.id).token + get "/#{Apartment::Tenant.current}/flat-pages" + } + + it 'associates flat_page with tour' do + expect(response).to have_http_status(200) + expect(json.size).to eq(FlatPage.count) + end + end + + # context 'flat page included in tours payload' do + # before { + # tour = create(:tour_with_flat_pages, theme: theme) + # tour.published = true + # tour.save + # get "/#{Apartment::Tenant.current}/tours?slug=#{tour.slug}" + # } + + # it 'creates tour with existing flat page' do + # expect(included.select { |d| d['type'] == 'tour_flat_pages' }.length).to eq(3) + # end + # end + + context 'get specific flat page by id' do + before { + Tour.first.update(published: true) + Tour.first.flat_pages << FlatPage.first + get "/#{Apartment::Tenant.current}/flat-pages/#{FlatPage.first.id}" + } + + it 'returns requested flat page' do + expect(response).to have_http_status(200) + end + end + + # valid payload + let(:valid_attributes) do + factory_to_json_api(FactoryBot.build(:flat_page)) + end + + describe 'POST /tenant/flat-pages' do + + context 'create page not authenticated' do + before { post "/#{Apartment::Tenant.current}/flat-pages", params: valid_attributes } + it 'returns status code 401' do + expect(response).to have_http_status(401) + end + end + + context 'when created by tour set admin' do + before { + User.first.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + cookies['auth'] = EcdsRailsAuthEngine::Login.find_by(user_id: User.first.id).token + post "/#{Apartment::Tenant.current}/flat-pages", params: valid_attributes + } + it 'creates a tour' do + expect(response).to have_http_status(201) + end + end + + context 'create without valid params' do + before { + User.first.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + cookies['auth'] = EcdsRailsAuthEngine::Login.find_by(user_id: User.first.id).token + post "/#{Apartment::Tenant.current}/flat-pages", params: { foo: 'bar' } + } + it 'returns unprocessable entity' do + expect(response).to have_http_status(422) + end + end + end + + describe 'PUT /tenant/flat-pages/' do + context 'update page not authenticated' do + before { put "/#{Apartment::Tenant.current}/flat-pages/#{FlatPage.first.id}", params: valid_attributes } + it 'returns status code 401' do + expect(response).to have_http_status(401) + end + end + + context 'when updated by tour set admin' do + before { + User.first.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + cookies['auth'] = EcdsRailsAuthEngine::Login.find_by(user_id: User.first.id).token + put "/#{Apartment::Tenant.current}/flat-pages/#{FlatPage.first.id}", params: valid_attributes + } + it 'creates a tour' do + expect(response).to have_http_status(200) + end + end + + context 'update without valid params' do + before { + User.first.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + invalid_attributes = { data: { type: 'flat_pages', attributes: { title: nil } } } + cookies['auth'] = EcdsRailsAuthEngine::Login.find_by(user_id: User.first.id).token + put "/#{Apartment::Tenant.current}/flat-pages/#{FlatPage.last.id}", params: invalid_attributes + } + it 'returns unprocessable entity' do + expect(response).to have_http_status(422) + end + end + + end + + describe 'DELETE /tenant/flat-pages/' do + context 'delete page not authenticated' do + before { + delete "/#{Apartment::Tenant.current}/flat-pages/#{FlatPage.first.id}", params: valid_attributes + } + it 'returns status code 401' do + expect(response).to have_http_status(401) + end + end + + context 'when deletes by tour set admin' do + before { + User.first.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + cookies['auth'] = EcdsRailsAuthEngine::Login.find_by(user_id: User.first.id).token + delete "/#{Apartment::Tenant.current}/flat-pages/#{FlatPage.first.id}", params: valid_attributes + } + it 'creates a tour' do + expect(response).to have_http_status(204) + end + end + end +end diff --git a/old_spcs/requests/v3/map_overlays_request_spec.rb b/old_spcs/requests/v3/map_overlays_request_spec.rb new file mode 100644 index 00000000..b183bcd8 --- /dev/null +++ b/old_spcs/requests/v3/map_overlays_request_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe "V3::MapOverlays", type: :request do + +end diff --git a/spec/requests/v3/media_spec.rb b/old_spcs/requests/v3/media_spec.rb.fix similarity index 99% rename from spec/requests/v3/media_spec.rb rename to old_spcs/requests/v3/media_spec.rb.fix index c6fc771b..b8922e4c 100644 --- a/spec/requests/v3/media_spec.rb +++ b/old_spcs/requests/v3/media_spec.rb.fix @@ -193,7 +193,7 @@ context 'update with valid data' do before { - valid_attributes[:data][:attributes]['original_image'] = Rack::Test::UploadedFile.new(Rails.root.join('spec/factories/test.jpg'), 'image/jpg') + valid_attributes[:data][:attributes]['original_image'] = Rack::Test::UploadedFile.new(Rails.root.join('spec/factories/images/test.jpg'), 'image/jpg') valid_attributes[:data][:attributes]['id'] = Medium.first.id put "/#{Apartment::Tenant.current}/media/#{Medium.first.id}", params: valid_attributes, headers: { Authorization: "Bearer #{User.last.login.oauth2_token}" } } diff --git a/spec/requests/v3/modes_spec.rb b/old_spcs/requests/v3/modes_spec.rb similarity index 100% rename from spec/requests/v3/modes_spec.rb rename to old_spcs/requests/v3/modes_spec.rb diff --git a/spec/requests/v3/stop_media_spec.rb b/old_spcs/requests/v3/stop_media_spec.rb.fix similarity index 97% rename from spec/requests/v3/stop_media_spec.rb rename to old_spcs/requests/v3/stop_media_spec.rb.fix index 327bf3ae..12cc6903 100644 --- a/spec/requests/v3/stop_media_spec.rb +++ b/old_spcs/requests/v3/stop_media_spec.rb.fix @@ -6,10 +6,7 @@ let!(:stop) { create_list(:stop_with_media, 1) } let!(:medium) { create(:medium) } let!(:new_medium) { create(:medium) } - let!(:user) { create(:user) } - let!(:login) { create(:login, user: user) } - let(:headers) { { Authorization: "Bearer #{login.oauth2_token}" } } - + let!(:user) { User.first } describe 'GET /stop-media' do context 'gets all stop media' do diff --git a/spec/requests/v3/stops_spec.rb b/old_spcs/requests/v3/stops_spec.rb similarity index 60% rename from spec/requests/v3/stops_spec.rb rename to old_spcs/requests/v3/stops_spec.rb index dfd46919..339f6723 100644 --- a/spec/requests/v3/stops_spec.rb +++ b/old_spcs/requests/v3/stops_spec.rb @@ -5,14 +5,22 @@ RSpec.describe 'V3::Stops API' do # Initialize the test data - let!(:login) { User.find_by(super: true).login } + let!(:user) { User.find_by(super: true) } # Test suite for GET /stops describe 'GET /stops' do context 'when stops exist' do - - before { get "/#{Apartment::Tenant.current}/stops" } - + + before { + user = create(:user) + user.update(super: false) + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + user.tours = [] + signed_cookie(user) + cookies['auth'] = EcdsRailsAuthEngine::Login.find_by(user_id: user.id).token + get "/#{Apartment::Tenant.current}/stops" + } + it 'returns status code 200' do expect(response).to have_http_status(200) end @@ -22,19 +30,21 @@ end end - context 'returns stops not associated with given tour' do - before { - get "/#{Apartment::Tenant.current}/stops?tour_id=#{Tour.last.id}" - } + # context 'returns stops not associated with given tour' do + # before { + # get "/#{Apartment::Tenant.current}/stops?tour_id=#{Tour.last.id}" + # } - it 'returns stops not part of given tour' do - expect(json.size).to eq(Stop.not_in_tour(Tour.last.id).count) - end - end + # it 'returns stops not part of given tour' do + # expect(json.size).to eq(Stop.not_in_tour(Tour.last.id).count) + # end + # end context 'get stop by slug and tour' do before { - get "/#{Apartment::Tenant.current}/tour-stops?slug=#{Stop.first.slug}&tour=#{Stop.first.tours.first.id}" + Tour.first.update(published: true) + Tour.first.stops << Stop.first + get "/#{Apartment::Tenant.current}/tour-stops?slug=#{Stop.first.slug}&tour=#{Tour.first.id}" } it 'returns stop in tour with slug' do @@ -46,36 +56,42 @@ # Test suite for GET /stops/:id describe 'GET /stops/:id' do - before { get "/#{Apartment::Tenant.current}/stops/#{Stop.first.id}" } + before { + Tour.first.update(published: true) + Tour.first.stops << Stop.first + get "/#{Apartment::Tenant.current}/stops/#{Stop.first.id}" + } context 'when tour stop exists' do it 'returns status code 200' do expect(response).to have_http_status(200) end - it 'returns the stop' do - expect(json['id']).to eq(Stop.first.id.to_s) - end + # For now, access to stop is through /tour-stop?slug=XX&tour=Y + # it 'returns the stop' do + # expect(json['id']).to eq(Stop.first.id.to_s) + # end - it 'has a meta_description based on description truncated and sanitized' do - expect(attributes['meta_description']).not_to include('

') - end + # it 'has a meta_description based on description truncated and sanitized' do + # expect(attributes['meta_description']).not_to include('

') + # end end - context 'when tour stop does not exist' do + context 'when stop does not exist' do before { get "/#{Apartment::Tenant.current}/stops/0" } it 'returns status code 404' do - expect(response).to have_http_status(404) + expect(response).to have_http_status(200) end - it 'returns a not found message' do - expect(response.body).to match(/Couldn't find Stop/) + it 'returns dummy stop' do + expect(json[:id]).to match('0') + expect(attributes[:title]).to be_nil end end end - describe 'GET /:tenant/stops?slug=:stop_slug' do + describe 'GET /:tenant/tour-stops?slug=:stop_slug' do let!(:stop) { Stop.second } let!(:tour) { stop.tours.first } let!(:original_slug) { stop.slug } @@ -84,8 +100,8 @@ context 'get stop after title change' do before { - stop.title = new_title - stop.save + tour.update(published: true) + stop.update(title: new_title) } before { get "/#{Apartment::Tenant.current}/tour-stops?slug=#{new_title.parameterize}&tour=#{tour.id}" } @@ -114,8 +130,11 @@ end context 'when request attributes are valid' do - before { User.first.update_attribute(:super, true) } - before { post "/#{Apartment::Tenant.current}/stops", params: valid_attributes, headers: { Authorization: "Bearer #{User.first.login.oauth2_token}" } } + before { + User.first.update_attribute(:super, true) + cookies['auth'] = EcdsRailsAuthEngine::Login.find_by(user_id: User.first.id).token + post "/#{Apartment::Tenant.current}/stops", params: valid_attributes + } it 'returns status code 201' do expect(response).to have_http_status(201) @@ -141,7 +160,10 @@ factory_to_json_api(FactoryBot.build(:stop, title: '3 Stacks')) end - before { put "/#{Apartment::Tenant.current}/stops/#{Stop.second.id}", params: valid_attributes, headers: { Authorization: "Bearer #{login.oauth2_token}" } } + before { + cookies['auth'] = EcdsRailsAuthEngine::Login.find_by(user_id: user.id).token + put "/#{Apartment::Tenant.current}/stops/#{Stop.second.id}", params: valid_attributes + } context 'when stop exists' do it 'returns status code 204' do @@ -155,21 +177,27 @@ end context 'when the stop does not exist' do - before { put "/#{Apartment::Tenant.current}/stops/0", params: valid_attributes, headers: { Authorization: "Bearer #{login.oauth2_token}" } } + before { + cookies['auth'] = EcdsRailsAuthEngine::Login.find_by(user_id: user.id).token + put "/#{Apartment::Tenant.current}/stops/0", params: valid_attributes + } it 'returns status code 404' do expect(response).to have_http_status(404) end it 'returns a not found message' do - expect(response.body).to match(/Couldn't find Stop/) + expect(response.body).to match(/Record not found/) end end end # Test suite for DELETE /stops/:id describe 'DELETE /stops/:id' do - before { delete "/#{Apartment::Tenant.current}/stops/#{Stop.last.id}", headers: { Authorization: "Bearer #{login.oauth2_token}" } } + before { + cookies['auth'] = EcdsRailsAuthEngine::Login.find_by(user_id: user.id).token + delete "/#{Apartment::Tenant.current}/stops/#{Stop.last.id}" + } it 'returns status code 204' do expect(response).to have_http_status(204) diff --git a/spec/requests/v3/themes_spec.rb b/old_spcs/requests/v3/themes_spec.rb similarity index 100% rename from spec/requests/v3/themes_spec.rb rename to old_spcs/requests/v3/themes_spec.rb diff --git a/spec/requests/v3/tour_set_users_spec.rb b/old_spcs/requests/v3/tour_set_users_spec.rb similarity index 100% rename from spec/requests/v3/tour_set_users_spec.rb rename to old_spcs/requests/v3/tour_set_users_spec.rb diff --git a/spec/requests/v3/tour_sets_spec.rb.fix b/old_spcs/requests/v3/tour_sets_spec.rb.fix similarity index 100% rename from spec/requests/v3/tour_sets_spec.rb.fix rename to old_spcs/requests/v3/tour_sets_spec.rb.fix diff --git a/spec/requests/v3/tour_stops_spec.rb b/old_spcs/requests/v3/tour_stops_spec.rb similarity index 57% rename from spec/requests/v3/tour_stops_spec.rb rename to old_spcs/requests/v3/tour_stops_spec.rb index f1900815..01d39771 100644 --- a/spec/requests/v3/tour_stops_spec.rb +++ b/old_spcs/requests/v3/tour_stops_spec.rb @@ -8,9 +8,11 @@ # Test suite for GET /stops describe 'GET /tour-stops' do - before { Apartment::Tenant.switch! TourSet.second.subdir } - # before { Tour.first.stops << Stop.last(5) } - before { get "/#{Apartment::Tenant.current}/tour-stops" } + before { + Apartment::Tenant.switch! TourSet.second.subdir + Tour.all.each { |tour| tour.update(published: true) } + get "/#{Apartment::Tenant.current}/tour-stops" + } context 'when stops exist' do it 'returns status code 200' do @@ -34,13 +36,16 @@ end describe 'GET /tour-stops?slug=slug&tour=X' do - before { Apartment::Tenant.switch! TourSet.second.subdir } - before { get "/#{Apartment::Tenant.current}/tour-stops?slug=#{Tour.first.stops.first.stop_slugs.first.slug}&tour=#{Tour.first.id}" } - - context 'get tour stop by slug and tour'do - it 'responds with the tour stop' do - expect(json['id'].to_i).to eq(Tour.first.stops.first.id) - end + before { + Apartment::Tenant.switch! TourSet.second.subdir + Tour.first.update(published: true) + get "/#{Apartment::Tenant.current}/tour-stops?slug=#{Tour.first.stops.first.stop_slugs.first.slug}&tour=#{Tour.first.id}" + } + + context 'get tour stop by slug and tour' do + it 'responds with the tour stop' do + expect(json['id'].to_i).to eq(Tour.first.stops.first.id) + end end end @@ -52,15 +57,17 @@ let!(:tour2) { Tour.last } let!(:stop2) { tour2.stops.last } let!(:new_title) { "#{Faker::Movies::Lebowski.character}" } - - before{ + + before { tour1.stops = [Stop.create(title: new_title)] + tour1.update(published: true) tour1.save tour2.stops = [Stop.create(title: new_title)] + tour2.update(published: true) tour2.save } - - + + context 'get stop with duplicate title/slug in correct tour' do before { get "/#{Apartment::Tenant.current}/tour-stops?slug=#{new_title.parameterize}&tour=#{tour1.id}" } it 'is true' do @@ -69,7 +76,6 @@ end end - context 'get stop with duplicate title/slug in correct tour' do before { get "/#{Apartment::Tenant.current}/tour-stops?slug=#{new_title.parameterize}&tour=#{tour2.id}" } it 'is true' do @@ -77,28 +83,5 @@ expect(json['relationships']['tour']['data']['id'].to_i).to eq(tour2.id) end end - - end - - describe 'DELETE /tour-stops' do - before { User.last.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) } - before { @stop = Stop.last } - before { @stop_count = Stop.count } - before { delete "/#{Apartment::Tenant.current}/tour-stops/#{TourStop.find_by(stop: @stop).id}", headers: { Authorization: "Bearer #{User.last.login.oauth2_token}" } } - - context 'when a tour-stop is deleted and the stop no longers belogs to a tour, the stop is deleted' do - it 'deletes the associated stop' do - expect(Stop.count).to eq @stop_count - 1 - end - end - - before { Tour.all.each { |t| t.stops << Stop.first } } - before { delete "/#{Apartment::Tenant.current}/tour-stops/#{TourStop.find_by(stop: Stop.first).id}", headers: { Authorization: "Bearer #{User.last.login.oauth2_token}" } } - - context 'when a tour-stop is deleted, the stop is not deleted if it belongs to other tours.' do - it 'deletes tour-stop but not the stop' do - expect(Stop.first.title).to eq(Stop.first.title) - end - end end end diff --git a/spec/requests/v3/tours_spec.rb b/old_spcs/requests/v3/tours_spec.rb similarity index 76% rename from spec/requests/v3/tours_spec.rb rename to old_spcs/requests/v3/tours_spec.rb index 7778c6b6..ac13555d 100644 --- a/spec/requests/v3/tours_spec.rb +++ b/old_spcs/requests/v3/tours_spec.rb @@ -108,7 +108,7 @@ } it 'returns nothing' do - expect(response).to have_http_status(404) + expect(response).to have_http_status(200) end end end @@ -122,9 +122,12 @@ before { Apartment::Tenant.switch! TourSet.find(TourSet.pluck(:id).sample).subdir } context 'when the post is valid and authenticated as non-tour set admin' do - before { User.last.update_attribute(:super, false) } - before { User.last.update_attribute(:tour_sets, []) } - before { post "/#{Apartment::Tenant.current}/tours", params: valid_attributes, headers: { Authorization: "Bearer #{User.last.login.oauth2_token}" } } + before { + User.last.update_attribute(:super, false) + User.last.update_attribute(:tour_sets, []) + cookies['auth'] = EcdsRailsAuthEngine::Login.find_by(user_id: User.last.id).token + post "/#{Apartment::Tenant.current}/tours", params: valid_attributes + } it 'returns status code 401' do expect(response).to have_http_status(401) @@ -132,8 +135,11 @@ end context 'when created by tour set admin' do - before { User.first.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) } - before { post "/#{Apartment::Tenant.current}/tours", params: valid_attributes, headers: { Authorization: "Bearer #{User.first.login.oauth2_token}" } } + before { + User.first.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + cookies['auth'] = EcdsRailsAuthEngine::Login.find_by(user_id: User.first.id).token + post "/#{Apartment::Tenant.current}/tours", params: valid_attributes + } it 'creates a tour' do expect(attributes['title']).to eq('Learn Elm') end @@ -150,7 +156,8 @@ before { # Tour.create!(published: true) User.first.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) - post "/#{Apartment::Tenant.current}/tours", params: invalid_attributes, headers: { Authorization: "Bearer #{User.first.login.oauth2_token}" } + cookies['auth'] = EcdsRailsAuthEngine::Login.find_by(user_id: User.first.id).token + post "/#{Apartment::Tenant.current}/tours", params: invalid_attributes } it 'returns status code 201' do @@ -170,7 +177,14 @@ end context 'when the record exists' do - before { put "/#{Apartment::Tenant.current}/tours/#{Tour.last.id}", params: valid_attributes, headers: { Authorization: "Bearer #{User.first.login.oauth2_token}" } } + before { + user = create(:user) + user.update(super: false) + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + signed_cookie(user) + cookies['auth'] = EcdsRailsAuthEngine::Login.find_by(user_id: user.id).token + put "/#{Apartment::Tenant.current}/tours/#{Tour.last.id}", params: valid_attributes + } it 'updates the record' do expect(json).not_to be_empty @@ -185,9 +199,12 @@ # Test suite for DELETE /atlanta/tours/:id describe 'DELETE /atlanta/tours/:id' do - before { Tour.create! } - before { User.first.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) } - before { delete "/#{Apartment::Tenant.current}/tours/#{Tour.last.id}", headers: { Authorization: "Bearer #{User.first.login.oauth2_token}" } } + before { + Tour.create! + User.first.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + cookies['auth'] = EcdsRailsAuthEngine::Login.find_by(user_id: User.first.id).token + delete "/#{Apartment::Tenant.current}/tours/#{Tour.last.id}" + } it 'returns status code 204' do expect(response).to have_http_status(204) @@ -197,9 +214,9 @@ describe 'Get //tours authenticated' do context 'tour set adim gets all the tours for that set' do before { - user = User.last - user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) - get "/#{Apartment::Tenant.current}/tours", headers: { Authorization: "Bearer #{user.login.oauth2_token}" } + User.last.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + cookies['auth'] = EcdsRailsAuthEngine::Login.find_by(user_id: User.last.id).token + get "/#{Apartment::Tenant.current}/tours" } it 'returns all the tours in the set' do @@ -210,16 +227,19 @@ context 'get tours as tour author' do before { user = User.first - user.super = false - user.tour_sets = [] - user.tours = [] + login = EcdsRailsAuthEngine::Login.find_by(user_id: user.id) + user.update(super: false, tour_sets: [], tours: []) + # user.super = false + # user.tour_sets = [] + # user.tours = [] user.save user.tours << Tour.first Tour.all.each do |t| t.published = false t.save end - get "/#{Apartment::Tenant.current}/tours", headers: { Authorization: "Bearer #{user.login.oauth2_token}" } + cookies['auth'] = login.token + get "/#{Apartment::Tenant.current}/tours" } it 'only returns tours user can edit' do diff --git a/spec/requests/v3/users_spec.fix b/old_spcs/requests/v3/users_spec.fix similarity index 100% rename from spec/requests/v3/users_spec.fix rename to old_spcs/requests/v3/users_spec.fix diff --git a/public/soundcloud.jpg b/public/soundcloud.jpg new file mode 100644 index 00000000..a18731df Binary files /dev/null and b/public/soundcloud.jpg differ diff --git a/spec/controllers/v1/tours_controller_spec.rb b/spec/controllers/v1/tours_controller_spec.rb deleted file mode 100644 index 721b5dda..00000000 --- a/spec/controllers/v1/tours_controller_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe V1::ToursController, type: :controller do - -end diff --git a/spec/controllers/v3/authenticated_controller_spec.rb b/spec/controllers/v3/authenticated_controller_spec.rb deleted file mode 100644 index f2935b2e..00000000 --- a/spec/controllers/v3/authenticated_controller_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe V3::AuthenticatedController, type: :controller do - -end diff --git a/spec/controllers/v3/flat_pages_controller_spec.rb b/spec/controllers/v3/flat_pages_controller_spec.rb index ec7f46b2..d425c993 100644 --- a/spec/controllers/v3/flat_pages_controller_spec.rb +++ b/spec/controllers/v3/flat_pages_controller_spec.rb @@ -1,129 +1,360 @@ require 'rails_helper' -# This spec was generated by rspec-rails when you ran the scaffold generator. -# It demonstrates how one might use RSpec to specify the controller code that -# was generated by Rails when you ran the scaffold generator. -# -# It assumes that the implementation code is generated by the rails scaffold -# generator. If you are using any extension libraries to generate different -# controller code, this generated spec may or may not pass. -# -# It only uses APIs available in rails and/or rspec-rails. There are a number -# of tools you can use to make these specs even more expressive, but we're -# sticking to rails and rspec-rails APIs to keep things simple and stable. -# -# Compared to earlier versions of this generator, there is very limited use of -# stubs and message expectations in this spec. Stubs are only used when there -# is no simpler way to get a handle on the object needed for the example. -# Message expectations are only used when there is no simpler way to specify -# that an instance is receiving a specific message. -# -# Also compared to earlier versions of this generator, there are no longer any -# expectations of assigns and templates rendered. These features have been -# removed from Rails core in Rails 5, but can be added back in via the -# `rails-controller-testing` gem. - RSpec.describe V3::FlatPagesController, type: :controller do + describe 'GET #index' do + it 'returns a 200 response with flat_pages connected to published tours' do + create_list(:tour_with_flat_pages, 5, theme: create(:theme), mode: create(:mode)) + Tour.first.update(published: true) if Tour.published.empty? + Tour.last.update(published: false) + get :index, params: { tenant: Apartment::Tenant.current } + expect(response.status).to eq(200) + expect(Tour.count).to be > Tour.published.count + json.each do |flat_page| + expect(FlatPage.find(flat_page[:id]).tours.any? { |tour| tour.published }) + end + expect(json.count).to be < FlatPage.count + end - # This should return the minimal set of attributes required to create a valid - # V3::FlatPage. As you add validations to V3::FlatPage, be sure to - # adjust the attributes here as well. - let(:valid_attributes) { - skip('Add a hash of attributes valid for your model') - } - - let(:invalid_attributes) { - skip('Add a hash of attributes invalid for your model') - } + it 'returns a 200 response with no flat_pages when request is authenticated by person with no access' do + create_list(:tour_with_flat_pages, 5, theme: create(:theme), mode: create(:mode)) + Tour.first.update(published: true) if Tour.published.empty? + Tour.last.update(published: false) if Tour.published.count == Tour.count + user = create(:user) + user.tour_sets = [] + user.tours = [] + signed_cookie(user) + get :index, params: { tenant: Apartment::Tenant.current } + expect(response.status).to eq(200) + expect(Tour.count).to be > Tour.published.count + json.each do |flat_page| + expect(FlatPage.find(flat_page[:id]).tours.any? { |tour| tour.published }) + end + expect(json.count).to be == FlatPage.all.reject {|fp| !fp.published}.count + end - # This should return the minimal set of values that should be in the session - # in order to pass any filters (e.g. authentication) defined in - # V3::FlatPagesController. Be sure to keep this updated too. - let(:valid_session) { {} } + it 'returns a 200 response with flat_pages when request is authenticated by tenant admin and tour is unpublished' do + tour = create(:tour_with_flat_pages, published: false) + tour.update(published: false) + user = create(:user) + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + signed_cookie(user) + get :index, params: { tenant: tour.tenant } + expect(FlatPage.count).to be > 1 + expect(response.status).to eq(200) + expect(json.count).to eq(FlatPage.count) + end - describe 'GET #index' do - it 'returns a success response' do - flat_page = FlatPage.create! valid_attributes - get :index, params: {}, session: valid_session - expect(response).to be_success + it 'returns a 200 response when request is authenticated by tour author and tour is unpublished' do + tour = create(:tour_with_flat_pages) + tour.update(published: false) + create_list(:flat_page, 7) + user = create(:user) + user.tour_sets = [] + user.tours << tour + signed_cookie(user) + get :index, params: { tenant: Apartment::Tenant.current } + expect(response.status).to eq(200) + expect(json.count).to eq(tour.flat_pages.count) + expect(json.count).to be < FlatPage.count end end describe 'GET #show' do - it 'returns a success response' do - flat_page = FlatPage.create! valid_attributes - get :show, params: { id: flat_page.to_param }, session: valid_session - expect(response).to be_success + it 'returns a 200 response that is empty stop' do + tour = create(:tour) + tour.update(published: false) + create_list(:flat_page, 3) + FlatPage.all.each { |flat_page| tour.flat_pages << flat_page } + # Make sure the flat page is only associated with the newly created tour + tour.flat_pages.last.update(tours: [tour]) + get :show, params: { tenant: Apartment::Tenant.current, id: tour.flat_pages.last.id } + expect(response.status).to eq(200) + expect(json[:id]).to eq(tour.flat_pages.last.id.to_s) + expect(attributes[:title]).to be_nil + end + + it 'returns a 200 response and stop when stop is part of published tour' do + tour = create(:tour) + tour.update(published: true) + create_list(:flat_page, 3) + FlatPage.all.each { |flat_page| tour.flat_pages << flat_page } + tour.flat_pages.last.update(tours: [tour]) + get :show, params: { tenant: Apartment::Tenant.current, id: tour.flat_pages.last.id } + expect(response.status).to eq(200) + expect(attributes[:title]).to eq(tour.flat_pages.last.title) + end + + it 'returns a 200 response that is empty stop when request is authenticated by someone w/o permission' do + tour = create(:tour) + tour.update(published: false) + create_list(:flat_page, 3) + FlatPage.all.each { |flat_page| tour.flat_pages << flat_page } + tour.flat_pages.last.update(tours: [tour]) + user = create(:user) + user.update(super: false) + user.tours = [] + user.tour_sets = [] + signed_cookie(user) + get :show, params: { tenant: Apartment::Tenant.current, id: tour.flat_pages.last.id } + expect(response.status).to eq(200) + expect(json[:id]).to eq(tour.flat_pages.last.id.to_s) + expect(attributes[:title]).to be_nil + end + + it 'returns a 200 response that is a stop when request is authenticated by a tour author' do + tour = create(:tour) + tour.update(published: false) + create_list(:flat_page, 3) + FlatPage.all.each { |flat_page| tour.flat_pages << flat_page } + tour.flat_pages.first.update(tours: [tour]) + user = create(:user) + user.update(super: false) + user.tours << tour + user.tour_sets = [] + signed_cookie(user) + get :show, params: { tenant: Apartment::Tenant.current, id: tour.flat_pages.first.id } + expect(response.status).to eq(200) + expect(attributes[:title]).to eq(tour.flat_pages.first.title) + end + + it 'returns a 200 response that is a stop when request is authenticated by a tenant admin' do + tour = create(:tour) + tour.update(published: false) + create_list(:flat_page, 3) + FlatPage.all.each { |flat_page| tour.flat_pages << flat_page } + tour.flat_pages.first.update(tours: [tour]) + user = create(:user) + user.update(super: false) + user.tours = [] + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + signed_cookie(user) + get :show, params: { tenant: Apartment::Tenant.current, id: tour.flat_pages.first.id } + expect(response.status).to eq(200) + expect(attributes[:title]).to eq(tour.flat_pages.first.title) + end + + it 'returns a 200 response that is a stop when request is authenticated by a super user' do + tour = create(:tour) + tour.update(published: false) + create_list(:flat_page, 3) + FlatPage.all.each { |flat_page| tour.flat_pages << flat_page } + tour.flat_pages.first.update(tours: [tour]) + user = create(:user) + user.update(super: true) + user.tours = [] + user.tour_sets = [] + signed_cookie(user) + get :show, params: { tenant: Apartment::Tenant.current, id: tour.flat_pages.first.id } + expect(response.status).to eq(200) + expect(attributes[:title]).to eq(tour.flat_pages.first.title) end end describe 'POST #create' do context 'with valid params' do - it 'creates a new V3::FlatPage' do - expect { - post :create, params: { v3_flat_page: valid_attributes }, session: valid_session - }.to change(FlatPage, :count).by(1) + it 'return 401 when unauthenciated' do + post :create, params: { data: { type: 'flat_pages', attributes: { title: 'Burrito FlatPage' } }, tenant: Apartment::Tenant.current } + expect(response.status).to eq(401) + end + + it 'return 401 when authenciated but not an admin for current tenant' do + user = create(:user) + user.update(super: false) + user.tour_sets = [] + user.tours = [] + signed_cookie(user) + post :create, params: { data: { type: 'flat_pages', attributes: { title: 'Burrito FlatPage' } }, tenant: Apartment::Tenant.current } + expect(response.status).to eq(401) end - it 'renders a JSON response with the new v3_flat_page' do + it 'return 201 when authenciated but an admin for current tenant' do + user = create(:user) + user.update(super: false) + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + signed_cookie(user) + original_flat_page_count = FlatPage.count + post :create, params: { data: { type: 'flat_pages', attributes: { title: 'Burrito FlatPage' } }, tenant: Apartment::Tenant.current } + expect(response.status).to eq(201) + expect(attributes[:title]).to eq('Burrito FlatPage') + expect(FlatPage.count).to eq(original_flat_page_count + 1) + end - post :create, params: { v3_flat_page: valid_attributes }, session: valid_session - expect(response).to have_http_status(:created) - expect(response.content_type).to eq('application/json') - expect(response.location).to eq(v3_flat_page_url(FlatPage.last)) + it 'return 201 when authenciated by super' do + user = create(:user) + user.tour_sets = [] + user.update(super: true) + signed_cookie(user) + original_flat_page_count = FlatPage.count + post :create, params: { data: { type: 'flat_pages', attributes: { title: 'Taco FlatPage' } }, tenant: Apartment::Tenant.current } + expect(response.status).to eq(201) + expect(attributes[:title]).to eq('Taco FlatPage') + expect(FlatPage.count).to eq(original_flat_page_count + 1) end - end - context 'with invalid params' do - it 'renders a JSON response with errors for the new v3_flat_page' do + it 'return 201 when authenciated by a tour author' do + user = create(:user) + user.tour_sets = [] + user.tours << Tour.last + user.update(super: false) + signed_cookie(user) + original_flat_page_count = FlatPage.count + post :create, params: { data: { type: 'flat_pages', attributes: { title: 'Elmyr' } }, tenant: Apartment::Tenant.current } + expect(response.status).to eq(201) + expect(attributes[:title]).to eq('Elmyr') + expect(FlatPage.count).to eq(original_flat_page_count + 1) + end - post :create, params: { v3_flat_page: invalid_attributes }, session: valid_session - expect(response).to have_http_status(:unprocessable_entity) - expect(response.content_type).to eq('application/json') + it 'return 422 when missing title' do + user = create(:user, super: true) + signed_cookie(user) + original_flat_page_count = FlatPage.count + post :create, params: { data: { type: 'flat_pages', attributes: {} }, tenant: Apartment::Tenant.current } + expect(response.status).to eq(422) + expect(errors).to include('Title can\'t be blank') + expect(FlatPage.count).to eq(original_flat_page_count) end end end describe 'PUT #update' do context 'with valid params' do - let(:new_attributes) { - skip('Add a hash of attributes valid for your model') - } - - it 'updates the requested v3_flat_page' do - flat_page = FlatPage.create! valid_attributes - put :update, params: { id: flat_page.to_param, v3_flat_page: new_attributes }, session: valid_session - flat_page.reload - skip('Add assertions for updated state') + it 'return 401 when unauthenciated' do + tour = create(:tour_with_flat_pages) + post :update, params: { id: tour.flat_pages.last.id, data: { type: 'flat_pages', attributes: { title: 'Burrito FlatPage' } }, tenant: Apartment::Tenant.current } + expect(response.status).to eq(401) end - it 'renders a JSON response with the v3_flat_page' do - flat_page = FlatPage.create! valid_attributes + it 'return 401 when authenciated but not an admin for current tenant' do + tour = create(:tour_with_flat_pages, published: false) + user = create(:user) + user.update(super: false) + user.tour_sets = [] + user.tours = [] + signed_cookie(user) + post :update, params: { id: tour.flat_pages.first.id, data: { type: 'flat_pages', attributes: { title: 'Burrito FlatPage' } }, tenant: Apartment::Tenant.current } + expect(response.status).to eq(401) + end - put :update, params: { id: flat_page.to_param, v3_flat_page: valid_attributes }, session: valid_session - expect(response).to have_http_status(:ok) - expect(response.content_type).to eq('application/json') + it 'return 200 and updated tour when authenciated but an admin for current tenant' do + tour = create(:tour_with_flat_pages, published: false) + user = create(:user) + user.update(super: false) + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + signed_cookie(user) + original_flat_page_title = FlatPage.find(tour.flat_pages.last.id).title + new_title = Faker::Name.unique.name + post :update, params: { id: tour.flat_pages.first.id, data: { type: 'flat_pages', attributes: { title: new_title } }, tenant: Apartment::Tenant.current } + expect(response.status).to eq(200) + expect(attributes[:title]).not_to eq(original_flat_page_title) + expect(attributes[:title]).to eq(new_title) + expect(FlatPage.find(tour.flat_pages.first.id).title).to eq(new_title) end - end - context 'with invalid params' do - it 'renders a JSON response with errors for the v3_flat_page' do - flat_page = FlatPage.create! valid_attributes + it 'return 200 and updated tour when authenciated by super' do + tour = create(:tour_with_flat_pages) + user = create(:user) + user.tour_sets = [] + user.update(super: true) + signed_cookie(user) + original_flat_page_title = FlatPage.find(tour.flat_pages.last.id).title + new_title = Faker::Name.unique.name + post :update, params: { id: tour.flat_pages.last.id, data: { type: 'flat_pages', attributes: { title: new_title } }, tenant: Apartment::Tenant.current } + expect(response.status).to eq(200) + expect(attributes[:title]).not_to eq(original_flat_page_title) + expect(attributes[:title]).to eq(new_title) + expect(FlatPage.find(tour.flat_pages.last.id).title).to eq(new_title) + end + + it 'return 200 and updated tour when authenciated by tour author' do + tour = create(:tour_with_flat_pages) + user = create(:user) + user.update(super: false) + user.tour_sets = [] + user.tours << tour + signed_cookie(user) + original_flat_page_title = FlatPage.find(tour.flat_pages.last.id).title + new_title = Faker::Name.unique.name + post :update, params: { id: tour.flat_pages.first.id, data: { type: 'flat_pages', attributes: { title: new_title } }, tenant: Apartment::Tenant.current } + expect(response.status).to eq(200) + expect(attributes[:title]).not_to eq(original_flat_page_title) + expect(attributes[:title]).to eq(new_title) + expect(FlatPage.find(tour.flat_pages.first.id).title).to eq(new_title) + end - put :update, params: { id: flat_page.to_param, v3_flat_page: invalid_attributes }, session: valid_session - expect(response).to have_http_status(:unprocessable_entity) - expect(response.content_type).to eq('application/json') + it 'returns 422 when title in nil' do + flat_page = create(:flat_page) + user = create(:user, super: true) + signed_cookie(user) + post :update, params: { id: flat_page.id, data: { type: 'flat_pages', attributes: { title: nil } }, tenant: Apartment::Tenant.current } + expect(response.status).to eq(422) + expect(errors).to include('Title can\'t be blank') end end + + # context 'with invalid params' do + # it 'renders a JSON response with errors for the tour' do + # tour = FlatPage.create! valid_attributes + + # put :update, params: { id: tour.to_param, tour: invalid_attributes } + # expect(response).to have_http_status(:unprocessable_entity) + # expect(response.content_type).to eq('application/json') + # end + # end end describe 'DELETE #destroy' do - it 'destroys the requested v3_flat_page' do - flat_page = FlatPage.create! valid_attributes - expect { - delete :destroy, params: { id: flat_page.to_param }, session: valid_session - }.to change(FlatPage, :count).by(-1) + it 'return 401 when unauthenciated' do + tour = create(:tour_with_flat_pages) + post :destroy, params: { id: Tour.find(tour.id).flat_pages.first.id, tenant: Apartment::Tenant.current } + expect(response.status).to eq(401) + end + + it 'return 401 when authenciated but not an admin for current tenant' do + tour = create(:tour_with_flat_pages) + user = create(:user) + user.update(super: false) + user.tour_sets = [] + signed_cookie(user) + post :destroy, params: { id: tour.flat_pages.first.id, tenant: Apartment::Tenant.current } + expect(response.status).to eq(401) + end + + it 'return 204 and one less tour when authenciated but an admin for current tenant' do + tour = create(:tour_with_flat_pages) + user = create(:user) + user.update(super: false) + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + signed_cookie(user) + flat_page_count = FlatPage.count + post :destroy, params: { id: tour.flat_pages.last.id, tenant: Apartment::Tenant.current } + expect(response.status).to eq(204) + expect(FlatPage.count).to eq(flat_page_count - 1) end - end + it 'return 204 and one less tour when authenciated by super' do + tour = create(:tour_with_flat_pages) + user = create(:user) + user.tour_sets = [] + user.update(super: true) + signed_cookie(user) + flat_page_count = FlatPage.count + post :destroy, params: { id: tour.flat_pages.first.id, tenant: Apartment::Tenant.current } + expect(response.status).to eq(204) + expect(FlatPage.count).to eq(flat_page_count - 1) + end + + it 'return 204 and one less tour when authenciated by tour author' do + tour = create(:tour_with_flat_pages) + user = create(:user) + user.update(super: false) + user.tour_sets = [] + user.tours << tour + signed_cookie(user) + new_title = Faker::Name.unique.name + flat_page_count = FlatPage.count + post :destroy, params: { id: tour.flat_pages.last.id, tenant: Apartment::Tenant.current } + expect(response.status).to eq(204) + expect(FlatPage.count).to eq(flat_page_count - 1) + end + end end diff --git a/spec/controllers/v3/map_icons_controller_spec.rb b/spec/controllers/v3/map_icons_controller_spec.rb new file mode 100644 index 00000000..35d607a5 --- /dev/null +++ b/spec/controllers/v3/map_icons_controller_spec.rb @@ -0,0 +1,185 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe V3::MapIconsController, type: :controller do + describe 'GET #index' do + it 'returns all map icons' do + create_list(:map_icon, rand(2..6)) + get :index, params: { tenant: Apartment::Tenant.current } + expect(json.count).to eq(MapIcon.count) + end + end + + describe 'GET #show' do + context 'unauthenticated and unauthorized' do + it 'returns empty MapIcon when not unauthenticated' do + tour = create(:tour, published: false, stops: create_list(:stop, 3)) + map_icon = create(:map_icon, stop: tour.stops.last) + get :show, params: { tenant: TourSet.first.subdir, id: map_icon.id } + expect(response.status).to eq(200) + expect(json[:id]).to eq(map_icon.id.to_s) + end + + it 'returns empty MapIcon when not unauthenticated but unauthorized' do + initial_tenant = Apartment::Tenant.current + tour = create(:tour, published: false, stops: create_list(:stop, 3)) + map_icon = create(:map_icon, stop: tour.stops.last) + user = create(:user, super: false) + user.tour_sets << create(:tour_set) + expect(user.tour_sets).not_to include TourSet.find_by(subdir: initial_tenant) + signed_cookie(user) + Apartment::Tenant.switch! initial_tenant + get :show, params: { tenant: initial_tenant, id: map_icon } + expect(response.status).to eq(200) + expect(json[:id]).to eq(map_icon.id.to_s) + end + end + + context 'authorized' do + it 'responds with 200 and a MapIcon when tour is published' do + tour = create(:tour, published: true, stops: create_list(:stop, 3)) + map_icon = create(:map_icon, stop: tour.stops.last) + get :show, params: { tenant: TourSet.first.subdir, id: map_icon.id } + expect(response.status).to eq(200) + expect(json[:id]).to eq(map_icon.id.to_s) + end + + it 'responds with 200 and a MapIcon when requested by a tour author' do + tour = create(:tour, published: false, stops: create_list(:stop, 3)) + map_icon = create(:map_icon, stop: tour.stops.last) + user = create(:user, super: false) + user.tours << tour + signed_cookie(user) + get :show, params: { tenant: Apartment::Tenant.current, id: map_icon } + expect(response.status).to eq(200) + expect(json[:id]).to eq(map_icon.id.to_s) + end + + it 'responds with 200 and a list of MapIcon when requested by tenant admin' do + tour = create(:tour, published: false, stops: create_list(:stop, 3)) + map_icon = create(:map_icon, stop: tour.stops.last) + user = create(:user, super: false) + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + signed_cookie(user) + get :show, params: { tenant: Apartment::Tenant.current, id: map_icon } + expect(response.status).to eq(200) + expect(json[:id]).to eq(map_icon.id.to_s) + end + + it 'responds with 200 and a MapIcon when requested by super' do + tour = create(:tour, published: false, stops: create_list(:stop, 3)) + map_icon = create(:map_icon, stop: tour.stops.last) + user = create(:user, super: true) + signed_cookie(user) + get :show, params: { tenant: Apartment::Tenant.current, id: map_icon } + expect(response.status).to eq(200) + expect(json[:id]).to eq(map_icon.id.to_s) + end + end + end + + describe 'POST #create' do + let(:tour) { create(:tour, stops: create_list(:stop, 2)) } + let(:valid_params) { + { + data: { + type: 'map_icon', + attributes: { + filename: Faker::File.file_name(dir: '', ext: 'png', directory_separator: ''), + base_sixty_four: File.read(Rails.root.join('spec/factories/images/icon_base64.txt')), + tour_id: tour.id + } + }, + tenant: Apartment::Tenant.current + } + } + + context 'unauthorized' do + it 'returns 401 when unauthenticated' do + initial_map_icon_count = MapIcon.count + post :create, params: valid_params + expect(response.status).to eq(401) + expect(MapIcon.count).to eq(initial_map_icon_count) + end + + it 'returns 401 when authenticated but unauthorized tenant admin' do + initial_map_icon_count = MapIcon.count + initial_tenant = Apartment::Tenant.current + user = create(:user, super: false) + user.tour_sets << create(:tour_set) + expect(user.tour_sets).not_to include TourSet.find_by(subdir: initial_tenant) + signed_cookie(user) + Apartment::Tenant.switch! initial_tenant + valid_params[:tenant] = initial_tenant + post :create, params: valid_params + expect(response.status).to eq(401) + expect(MapIcon.count).to eq(initial_map_icon_count) + end + end + + context 'authorized' do + it 'creates when request by super' do + user = create(:user, super: true) + signed_cookie(user) + expect { + post :create, params: valid_params + }.to change(MapIcon, :count).by(1) + end + + it 'creates when request by current tenant admin' do + user = create(:user, super: false) + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + signed_cookie(user) + expect { + post :create, params: valid_params + }.to change(MapIcon, :count).by(1) + end + + it 'creates when request by tour author' do + user = create(:user, super: false) + user.tours << tour + signed_cookie(user) + expect { + post :create, params: valid_params + }.to change(MapIcon, :count).by(1) + end + end + + context 'invalid params' do + let(:tour) { create(:tour, stops: create_list(:stop, 2)) } + let(:invalid_params) { + { + data: { + type: 'map_icon', + attributes: { + filename: Faker::File.file_name(dir: '', ext: 'png', directory_separator: ''), + base_sixty_four: File.read(Rails.root.join('spec/factories/images/atl_base64.txt')), + tour_id: tour.id + } + }, + tenant: Apartment::Tenant.current + } + } + + it 'returns 422 with invalid params' do + initial_map_icon_count = MapIcon.count + user = create(:user, super: true) + signed_cookie(user) + post :create, params: { tenant: Apartment::Tenant.current, data: {} } + expect(response).to have_http_status(:unprocessable_entity) + expect(MapIcon.count).to eq(initial_map_icon_count) + end + + it 'returns 422 and size error message' do + initial_map_icon_count = MapIcon.count + user = create(:user, super: true) + signed_cookie(user) + post :create, params: invalid_params + expect(response).to have_http_status(:unprocessable_entity) + expect(MapIcon.count).to eq(initial_map_icon_count) + expect(errors).to include('Icons should be no bigger that 80 by 80 pixels') + end + end + end +end diff --git a/spec/controllers/v3/map_overlays_controller_spec.rb b/spec/controllers/v3/map_overlays_controller_spec.rb new file mode 100755 index 00000000..e8175c3c --- /dev/null +++ b/spec/controllers/v3/map_overlays_controller_spec.rb @@ -0,0 +1,160 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe V3::MapOverlaysController, type: :controller do + describe 'GET #index' do + context 'unauthenticated and unauthorized' do + it 'returns empty MapOverlay when not unauthenticated' do + tour = create(:tour, published: false, stops: create_list(:stop, 3)) + map_overlay = create(:map_overlay, tour: tour) + get :show, params: { tenant: TourSet.first.subdir, id: map_overlay.id } + expect(response.status).to eq(200) + expect(json[:id]).to eq(map_overlay.id.to_s) + expect(attributes[:south]).not_to eq(map_overlay.south.to_f.to_s) + expect(attributes[:east]).to be nil + end + + it 'returns empty MapOverlay when not unauthenticated but unauthorized' do + initial_tenant = Apartment::Tenant.current + tour = create(:tour, published: false, stops: create_list(:stop, 3)) + map_overlay = create(:map_overlay, tour: tour) + user = create(:user, super: false) + user.tour_sets << create(:tour_set) + expect(user.tour_sets).not_to include TourSet.find_by(subdir: initial_tenant) + signed_cookie(user) + Apartment::Tenant.switch! initial_tenant + get :show, params: { tenant: initial_tenant, id: map_overlay } + expect(response.status).to eq(200) + expect(json[:id]).to eq(map_overlay.id.to_s) + expect(attributes[:north]).not_to eq(map_overlay.north.to_f.to_s) + expect(attributes[:west]).to be nil + end + end + + context 'authorized' do + it 'responds with 200 and a MapOverlay when tour is published' do + tour = create(:tour, published: true, stops: create_list(:stop, 3)) + map_overlay = create(:map_overlay, tour: tour) + get :show, params: { tenant: TourSet.first.subdir, id: map_overlay.id } + expect(response.status).to eq(200) + expect(json[:id]).to eq(map_overlay.id.to_s) + expect(attributes[:south]).to eq(map_overlay.south.to_f.to_s) + end + + it 'responds with 200 and a MapOverlay when requested by a tour author' do + tour = create(:tour, published: false, stops: create_list(:stop, 3)) + map_overlay = create(:map_overlay, tour: tour) + user = create(:user, super: false) + user.tours << tour + signed_cookie(user) + get :show, params: { tenant: Apartment::Tenant.current, id: map_overlay } + expect(response.status).to eq(200) + expect(json[:id]).to eq(map_overlay.id.to_s) + expect(attributes[:north]).to eq(map_overlay.north.to_f.to_s) + end + + it 'responds with 200 and a list of MapOverlay when requested by tenant admin' do + tour = create(:tour, published: false, stops: create_list(:stop, 3)) + map_overlay = create(:map_overlay, tour: tour) + user = create(:user, super: false) + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + signed_cookie(user) + get :show, params: { tenant: Apartment::Tenant.current, id: map_overlay } + expect(response.status).to eq(200) + expect(json[:id]).to eq(map_overlay.id.to_s) + expect(attributes[:north]).to eq(map_overlay.north.to_f.to_s) + end + + it 'responds with 200 and a MapOverlay when requested by super' do + tour = create(:tour, published: false, stops: create_list(:stop, 3)) + map_overlay = create(:map_overlay, tour: tour) + user = create(:user, super: true) + signed_cookie(user) + get :show, params: { tenant: Apartment::Tenant.current, id: map_overlay } + expect(response.status).to eq(200) + expect(json[:id]).to eq(map_overlay.id.to_s) + expect(attributes[:north]).to eq(map_overlay.north.to_f.to_s) + end + end + end + + describe 'POST #create' do + let(:tour) { create(:tour, stops: create_list(:stop, 2)) } + let(:valid_params) { + { + data: { + type: 'map_overlay', + attributes: { + filename: Faker::File.file_name(dir: '', ext: 'png', directory_separator: ''), + base_sixty_four: File.read(Rails.root.join('spec/factories/base64_image.txt')), + tour_id: tour.id + } + }, + tenant: Apartment::Tenant.current + } + } + + context 'unauthorized' do + it 'returns 401 when unauthenticated' do + initial_map_overlay_count = MapOverlay.count + post :create, params: valid_params + expect(response.status).to eq(401) + expect(MapOverlay.count).to eq(initial_map_overlay_count) + end + + it 'returns 401 when authenticated but unauthorized tenant admin' do + initial_map_overlay_count = MapOverlay.count + initial_tenant = Apartment::Tenant.current + user = create(:user, super: false) + user.tour_sets << create(:tour_set) + expect(user.tour_sets).not_to include TourSet.find_by(subdir: initial_tenant) + signed_cookie(user) + Apartment::Tenant.switch! initial_tenant + valid_params[:tenant] = initial_tenant + post :create, params: valid_params + expect(response.status).to eq(401) + expect(MapOverlay.count).to eq(initial_map_overlay_count) + end + end + + context 'authorized' do + it 'creates when request by super' do + user = create(:user, super: true) + signed_cookie(user) + expect { + post :create, params: valid_params + }.to change(MapOverlay, :count).by(1) + end + + it 'creates when request by current tenant admin' do + user = create(:user, super: false) + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + signed_cookie(user) + expect { + post :create, params: valid_params + }.to change(MapOverlay, :count).by(1) + end + + it 'creates when request by tour author' do + user = create(:user, super: false) + user.tours << tour + signed_cookie(user) + expect { + post :create, params: valid_params + }.to change(MapOverlay, :count).by(1) + end + end + + context 'invalid params' do + it 'returns 422 with invalid params' do + initial_map_overlay_count = MapOverlay.count + user = create(:user, super: true) + signed_cookie(user) + post :create, params: { tenant: Apartment::Tenant.current, data: {} } + expect(response).to have_http_status(:unprocessable_entity) + expect(MapOverlay.count).to eq(initial_map_overlay_count) + end + end + end +end diff --git a/spec/controllers/v3/media_controller_spec.rb b/spec/controllers/v3/media_controller_spec.rb index 8c370f78..0e2116af 100644 --- a/spec/controllers/v3/media_controller_spec.rb +++ b/spec/controllers/v3/media_controller_spec.rb @@ -2,130 +2,345 @@ require 'rails_helper' -# This spec was generated by rspec-rails when you ran the scaffold generator. -# It demonstrates how one might use RSpec to specify the controller code that -# was generated by Rails when you ran the scaffold generator. -# -# It assumes that the implementation code is generated by the rails scaffold -# generator. If you are using any extension libraries to generate different -# controller code, this generated spec may or may not pass. -# -# It only uses APIs available in rails and/or rspec-rails. There are a number -# of tools you can use to make these specs even more expressive, but we're -# sticking to rails and rspec-rails APIs to keep things simple and stable. -# -# Compared to earlier versions of this generator, there is very limited use of -# stubs and message expectations in this spec. Stubs are only used when there -# is no simpler way to get a handle on the object needed for the example. -# Message expectations are only used when there is no simpler way to specify -# that an instance is receiving a specific message. -# -# Also compared to earlier versions of this generator, there are no longer any -# expectations of assigns and templates rendered. These features have been -# removed from Rails core in Rails 5, but can be added back in via the -# `rails-controller-testing` gem. - RSpec.describe V3::MediaController, type: :controller do - # This should return the minimal set of attributes required to create a valid - # Medium. As you add validations to Medium, be sure to - # adjust the attributes here as well. - let(:valid_attributes) { - skip('Add a hash of attributes valid for your model') + let(:valid_params) { + { + data: { + type: 'media', + attributes: { + base_sixty_four: File.read(Rails.root.join('spec/factories/base64_image.txt')), + filename: Faker::File.file_name(dir: '', ext: 'png', directory_separator: '') + } + }, + tenant: Apartment::Tenant.current + } } - let(:invalid_attributes) { - skip('Add a hash of attributes invalid for your model') - } + if ENV['DB_ADAPTER'] == 'mysql2' + skip('Fix this spec for MySQL. Something to do with it being transactional') + else + describe 'GET #index' do - # This should return the minimal set of values that should be in the session - # in order to pass any filters (e.g. authentication) defined in - # MediaController. Be sure to keep this updated too. - let(:valid_session) { {} } + it 'returns a success response' do + create_list(:medium, 5) + tour = create(:tour, published: true) + Medium.all.each { |m| tour.media << m } + get :index, params: { tenant: Apartment::Tenant.current } + expect(response.status).to eq(200) + expect(json.count).to eq(tour.media.count) + end - describe 'GET #index' do - it 'returns a success response' do - medium = Medium.create! valid_attributes - get :index, params: {}, session: valid_session - expect(response).to be_success - end - end + it 'returns only media associated with a public tour' do + published_tour = create(:tour, published: true) + create_list(:medium, rand(1..8)).each { |m| published_tour.media << m } + unpublished_tour = create(:tour, published: false) + create_list(:medium, rand(1..8)).each { |m| unpublished_tour.media << m } + get :index, params: { tenant: Apartment::Tenant.current } + expect(json.count).to eq(Tour.published.map { |t| t.media.count }.sum) + expect(json.count).to be < Medium.count + end + + it 'returns all media when requested by tenant admin' do + published_tour = create(:tour, published: true) + create_list(:medium, rand(1..8)).each { |m| published_tour.media << m } + unpublished_tour = create(:tour, published: false) + create_list(:medium, rand(1..8)).each { |m| unpublished_tour.media << m } + user = create(:user) + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + signed_cookie(user) + get :index, params: { tenant: Apartment::Tenant.current } + expect(response.status).to eq(200) + expect(json.count).to eq(Medium.count) + expect(Medium.count).to be > Tour.published.map { |t| t.media.count }.sum + end - describe 'GET #show' do - it 'returns a success response' do - medium = Medium.create! valid_attributes - get :show, params: { id: medium.to_param }, session: valid_session - expect(response).to be_success + it 'returns a paginated list when page parameter is persent' do + user = create(:user, super: true) + signed_cookie(user) + published_tour = create(:tour, published: true) + create_list(:medium, 35).each { |m| published_tour.media << m } + get :index, params: { tenant: Apartment::Tenant.current, page: '2' } + expect(json.count).to eq(10) + end end - end - describe 'POST #create' do - context 'with valid params' do - it 'creates a new Medium' do - expect { - post :create, params: { medium: valid_attributes }, session: valid_session - }.to change(Medium, :count).by(1) + describe 'GET #show' do + it 'returns 401 when medium is not published by a tour or stop' do + medium = create(:medium) + get :show, params: { tenant: Apartment::Tenant.current, id: medium.id } + expect(response.status).to eq(200) + expect(medium.published).to be false + expect(attributes[:title]).to eq('....') + end + + it 'returns the medium when associated with published stop' do + tour = create(:tour, published: true) + stop = create(:stop) + medium = create(:medium) + tour.stops << stop + stop.media << medium + get :show, params: { tenant: Apartment::Tenant.current, id: medium.id } + expect(medium.published).to be true + expect(response.status).to eq(200) + expect(attributes[:title]).to eq(medium.title) end - it 'renders a JSON response with the new medium' do + it 'returns the medium when unpublished but requested is authorized' do + medium = create(:medium) + medium.save + expect(medium.file.attached?).to be true + user = create(:user) + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + signed_cookie(user) + get :show, params: { tenant: Apartment::Tenant.current, id: medium.id } + expect(medium.published).to be false + expect(response.status).to eq(200) + expect(attributes[:title]).to eq(medium.title) + end - post :create, params: { medium: valid_attributes }, session: valid_session - expect(response).to have_http_status(:created) - expect(response.content_type).to eq('application/json') - expect(response.location).to eq(medium_url(Medium.last)) + it 'returns the medium that if a gif and requested is authorized' do + medium = create(:medium, base_sixty_four: File.read(Rails.root.join('spec/factories/images/gif_base64.txt')), filename: Faker::File.file_name(dir: '', ext: 'gif', directory_separator: '')) + medium.save + expect(medium.file.attached?).to be true + user = create(:user, super: true) + signed_cookie(user) + get :show, params: { tenant: Apartment::Tenant.current, id: medium.id } + expect(response.status).to eq(200) + expect(attributes[:files][:mobile]).to end_with 'gif' end end - context 'with invalid params' do - it 'renders a JSON response with errors for the new medium' do + describe 'POST #create' do + context 'with valid params and request is authorized' do + it 'creates a new Medium' do + user = create(:user) + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + signed_cookie(user) + expect { + post :create, params: valid_params + }.to change(Medium, :count).by(1) + end - post :create, params: { medium: invalid_attributes }, session: valid_session - expect(response).to have_http_status(:unprocessable_entity) - expect(response.content_type).to eq('application/json') + it 'renders a JSON response with the new medium when super' do + user = create(:user, super: true) + signed_cookie(user) + post :create, params: valid_params + expect(response).to have_http_status(:created) + expect(response.content_type).to eq('application/json; charset=utf-8') + expect(json[:id]).to eq(Medium.last.id.to_s) + end + + it 'renders a JSON response with the new medium when tenant admin' do + user = create(:user) + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + signed_cookie(user) + post :create, params: valid_params + expect(response).to have_http_status(:created) + expect(response.content_type).to eq('application/json; charset=utf-8') + expect(json[:id]).to eq(Medium.last.id.to_s) + end + + it 'renders a JSON response with the new medium when tour author' do + user = create(:user) + tour = create(:tour) + user.tour_sets = [] + user.tours << tour + signed_cookie(user) + post :create, params: valid_params + expect(response).to have_http_status(:created) + expect(response.content_type).to eq('application/json; charset=utf-8') + expect(json[:id]).to eq(Medium.last.id.to_s) + end end - end - end - describe 'PUT #update' do - context 'with valid params' do - let(:new_attributes) { - skip('Add a hash of attributes valid for your model') - } + context 'with invalid params' do + it 'renders a JSON response with errors for the new medium' do + user = create(:user) + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + signed_cookie(user) + post :create, params: { medium: 'invalid_params', tenant: Apartment::Tenant.current } + expect(response).to have_http_status(:unprocessable_entity) + expect(response.content_type).to eq('application/json; charset=utf-8') + end - it 'updates the requested medium' do - medium = Medium.create! valid_attributes - put :update, params: { id: medium.to_param, medium: new_attributes }, session: valid_session - medium.reload - skip('Add assertions for updated state') + it 'renders a JSON response with errors for the new medium when file is jp2' do + user = create(:user, super: true) + signed_cookie(user) + jp2_params = valid_params.clone + jp2_params[:data][:attributes] = { + base_sixty_four: File.read(Rails.root.join('spec/factories/images/jp2_base64.txt')), + filename: Faker::File.file_name(dir: '', ext: 'jp2', directory_separator: '') + } + post :create, params: jp2_params + expect(response).to have_http_status(:unprocessable_entity) + expect(errors).to include('JPEG 2000 fils are not supported. Plese convert the image to a reqular JPEG or WebP format.') + end end - it 'renders a JSON response with the medium' do - medium = Medium.create! valid_attributes + context 'with unauthenticated request' do + it 'responds unauthorized when unauthenticated' do + post :create, params: valid_params + expect(response).to have_http_status(:unauthorized) + end - put :update, params: { id: medium.to_param, medium: valid_attributes }, session: valid_session - expect(response).to have_http_status(:ok) - expect(response.content_type).to eq('application/json') + it 'responds unauthorized when authenticated but not authorized' do + initial_tenant = Apartment::Tenant.current + user = create(:user) + user.tour_sets << create(:tour_set) + signed_cookie(user) + Apartment::Tenant.switch! initial_tenant + post :create, params: valid_params + expect(response).to have_http_status(:unauthorized) + end end end - context 'with invalid params' do - it 'renders a JSON response with errors for the medium' do - medium = Medium.create! valid_attributes + describe 'PUT #update' do + context 'with valid params and request is authorized' do - put :update, params: { id: medium.to_param, medium: invalid_attributes }, session: valid_session - expect(response).to have_http_status(:unprocessable_entity) - expect(response.content_type).to eq('application/json') + it 'renders a JSON response with the new medium when super' do + medium = create(:medium) + update_params = JSON.parse(ActiveModelSerializers::Adapter::JsonApi.new(V3::MediumSerializer.new(medium)).to_json).with_indifferent_access + update_params[:tenant] = Apartment::Tenant.current + update_params[:id] = update_params[:data][:id] + user = create(:user, super: true) + initial_title = update_params[:data][:attributes][:title] + update_params[:data][:attributes][:title] = Faker::Movies::HitchhikersGuideToTheGalaxy.location + signed_cookie(user) + post :update, params: update_params + expect(response).to have_http_status(:ok) + expect(attributes[:title]).not_to eq(initial_title) + end + + it 'renders a JSON response with the new medium when tenant admin' do + medium = create(:medium) + update_params = JSON.parse(ActiveModelSerializers::Adapter::JsonApi.new(V3::MediumSerializer.new(medium)).to_json).with_indifferent_access + update_params[:tenant] = Apartment::Tenant.current + update_params[:id] = update_params[:data][:id] + user = create(:user, super: false) + user.tours = [] + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + initial_title = update_params[:data][:attributes][:title] + update_params[:data][:attributes][:title] = Faker::Movies::HitchhikersGuideToTheGalaxy.location + signed_cookie(user) + post :update, params: update_params + expect(response).to have_http_status(:ok) + expect(attributes[:title]).not_to eq(initial_title) + end + + it 'renders a JSON response with the new medium when tour author' do + medium = create(:medium) + update_params = JSON.parse(ActiveModelSerializers::Adapter::JsonApi.new(V3::MediumSerializer.new(medium)).to_json).with_indifferent_access + update_params[:tenant] = Apartment::Tenant.current + update_params[:id] = update_params[:data][:id] + user = create(:user, super: false) + user.tour_sets = [] + user.tours << create(:tour) + initial_title = update_params[:data][:attributes][:title] + update_params[:data][:attributes][:title] = Faker::Movies::HitchhikersGuideToTheGalaxy.location + signed_cookie(user) + post :update, params: update_params + expect(response).to have_http_status(:ok) + expect(attributes[:title]).not_to eq(initial_title) + end + end + + context 'with invalid params' do + it 'renders a JSON response with errors for the new medium' do + user = create(:user, super: true) + signed_cookie(user) + post :update, params: { id: create(:medium).id, data: { type: 'medium', attributes: { filename: nil } }, tenant: Apartment::Tenant.current } + expect(response).to have_http_status(:unprocessable_entity) + expect(response.content_type).to eq('application/json; charset=utf-8') + end + end + + context 'with unauthenticated request' do + it 'responds unauthorized when unauthenticated' do + medium = create(:medium) + update_params = JSON.parse(ActiveModelSerializers::Adapter::JsonApi.new(V3::MediumSerializer.new(medium)).to_json).with_indifferent_access + update_params[:tenant] = Apartment::Tenant.current + update_params[:id] = update_params[:data][:id] + post :update, params: update_params + expect(response).to have_http_status(:unauthorized) + end + + it 'responds unauthorized when authenticated but not authorized' do + initial_tenant = Apartment::Tenant.current + medium = create(:medium) + update_params = JSON.parse(ActiveModelSerializers::Adapter::JsonApi.new(V3::MediumSerializer.new(medium)).to_json).with_indifferent_access + update_params[:tenant] = Apartment::Tenant.current + update_params[:id] = update_params[:data][:id] + user = create(:user, super: false) + user.tour_sets << create(:tour_set) + signed_cookie(user) + Apartment::Tenant.switch! initial_tenant + post :update, params: update_params + expect(response).to have_http_status(:unauthorized) + end end end - end - describe 'DELETE #destroy' do - it 'destroys the requested medium' do - medium = Medium.create! valid_attributes - expect { - delete :destroy, params: { id: medium.to_param }, session: valid_session - }.to change(Medium, :count).by(-1) + describe 'DELETE #destroy' do + context 'authenticated and authorized' do + it 'destroys the requested medium when request from super' do + medium = create(:medium) + user = create(:user, super: true) + signed_cookie(user) + expect { + delete :destroy, params: { id: medium.to_param, tenant: Apartment::Tenant.current } + }.to change(Medium, :count).by(-1) + end + + it 'destroys the requested medium when request from tenant admin' do + medium = create(:medium) + user = create(:user, super: false) + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + signed_cookie(user) + expect { + delete :destroy, params: { id: medium.to_param, tenant: Apartment::Tenant.current } + }.to change(Medium, :count).by(-1) + end + + it 'destroys the requested medium when request from tour author' do + medium = create(:medium) + user = create(:user, super: false) + user.tour_sets = [] + user.tours << create(:tour) + signed_cookie(user) + expect { + delete :destroy, params: { id: medium.to_param, tenant: Apartment::Tenant.current } + }.to change(Medium, :count).by(-1) + end + end + + context 'authenticated but not authorized' do + it 'returns unauthorized' do + initial_tenant = Apartment::Tenant.current + medium = create(:medium) + user = create(:user, super: false) + Apartment::Tenant.reset + tour_set = create(:tour_set) + user.tour_sets << tour_set + signed_cookie(user) + Apartment::Tenant.switch! initial_tenant + initial_media_count = Medium.count + delete :destroy, params: { id: medium.to_param, tenant: Apartment::Tenant.current } + expect(response).to have_http_status(:unauthorized) + expect(Medium.count).to eq(initial_media_count) + end + end + + context 'unauthenticated' do + it 'returns unauthorized' do + medium = create(:medium) + initial_media_count = Medium.count + delete :destroy, params: { id: medium.to_param, tenant: Apartment::Tenant.current } + expect(response).to have_http_status(:unauthorized) + expect(Medium.count).to eq(initial_media_count) + end + end end end - end diff --git a/spec/controllers/v3/modes_controller_spec.rb b/spec/controllers/v3/modes_controller_spec.rb index 633e7aa2..6a103de3 100644 --- a/spec/controllers/v3/modes_controller_spec.rb +++ b/spec/controllers/v3/modes_controller_spec.rb @@ -2,130 +2,25 @@ require 'rails_helper' -# This spec was generated by rspec-rails when you ran the scaffold generator. -# It demonstrates how one might use RSpec to specify the controller code that -# was generated by Rails when you ran the scaffold generator. -# -# It assumes that the implementation code is generated by the rails scaffold -# generator. If you are using any extension libraries to generate different -# controller code, this generated spec may or may not pass. -# -# It only uses APIs available in rails and/or rspec-rails. There are a number -# of tools you can use to make these specs even more expressive, but we're -# sticking to rails and rspec-rails APIs to keep things simple and stable. -# -# Compared to earlier versions of this generator, there is very limited use of -# stubs and message expectations in this spec. Stubs are only used when there -# is no simpler way to get a handle on the object needed for the example. -# Message expectations are only used when there is no simpler way to specify -# that an instance is receiving a specific message. -# -# Also compared to earlier versions of this generator, there are no longer any -# expectations of assigns and templates rendered. These features have been -# removed from Rails core in Rails 5, but can be added back in via the -# `rails-controller-testing` gem. - RSpec.describe V3::ModesController, type: :controller do - # This should return the minimal set of attributes required to create a valid - # Mode. As you add validations to Mode, be sure to - # adjust the attributes here as well. - let(:valid_attributes) { - skip('Add a hash of attributes valid for your model') - } - - let(:invalid_attributes) { - skip('Add a hash of attributes invalid for your model') - } - - # This should return the minimal set of values that should be in the session - # in order to pass any filters (e.g. authentication) defined in - # ModesController. Be sure to keep this updated too. - let(:valid_session) { {} } describe 'GET #index' do it 'returns a success response' do - mode = Mode.create! valid_attributes - get :index, params: {}, session: valid_session - expect(response).to be_success + tour_set = create(:tour_set) + get :index, params: { tenant: tour_set.subdir } + expect(response.status).to eq(200) + expect(json.count).to eq(4) end end describe 'GET #show' do it 'returns a success response' do - mode = Mode.create! valid_attributes - get :show, params: { id: mode.to_param }, session: valid_session - expect(response).to be_success - end - end - - describe 'POST #create' do - context 'with valid params' do - it 'creates a new Mode' do - expect { - post :create, params: { mode: valid_attributes }, session: valid_session - }.to change(Mode, :count).by(1) - end - - it 'renders a JSON response with the new mode' do - - post :create, params: { mode: valid_attributes }, session: valid_session - expect(response).to have_http_status(:created) - expect(response.content_type).to eq('application/json') - expect(response.location).to eq(mode_url(Mode.last)) - end - end - - context 'with invalid params' do - it 'renders a JSON response with errors for the new mode' do - - post :create, params: { mode: invalid_attributes }, session: valid_session - expect(response).to have_http_status(:unprocessable_entity) - expect(response.content_type).to eq('application/json') - end - end - end - - describe 'PUT #update' do - context 'with valid params' do - let(:new_attributes) { - skip('Add a hash of attributes valid for your model') - } - - it 'updates the requested mode' do - mode = Mode.create! valid_attributes - put :update, params: { id: mode.to_param, mode: new_attributes }, session: valid_session - mode.reload - skip('Add assertions for updated state') - end - - it 'renders a JSON response with the mode' do - mode = Mode.create! valid_attributes - - put :update, params: { id: mode.to_param, mode: valid_attributes }, session: valid_session - expect(response).to have_http_status(:ok) - expect(response.content_type).to eq('application/json') - end - end - - context 'with invalid params' do - it 'renders a JSON response with errors for the mode' do - mode = Mode.create! valid_attributes - - put :update, params: { id: mode.to_param, mode: invalid_attributes }, session: valid_session - expect(response).to have_http_status(:unprocessable_entity) - expect(response.content_type).to eq('application/json') - end - end - end - - describe 'DELETE #destroy' do - it 'destroys the requested mode' do - mode = Mode.create! valid_attributes - expect { - delete :destroy, params: { id: mode.to_param }, session: valid_session - }.to change(Mode, :count).by(-1) + tour_set = create(:tour_set) + Apartment::Tenant.switch! tour_set.subdir + get :show, params: { id: Mode.first.to_param, tenant: tour_set.subdir } + expect(response.status).to eq(200) + expect(attributes[:title]).to eq(Mode.first.title) end end - end diff --git a/spec/controllers/v3/stops_controller_spec.rb b/spec/controllers/v3/stops_controller_spec.rb index 155cc5ad..929cd1f9 100644 --- a/spec/controllers/v3/stops_controller_spec.rb +++ b/spec/controllers/v3/stops_controller_spec.rb @@ -2,130 +2,354 @@ require 'rails_helper' -# This spec was generated by rspec-rails when you ran the scaffold generator. -# It demonstrates how one might use RSpec to specify the controller code that -# was generated by Rails when you ran the scaffold generator. -# -# It assumes that the implementation code is generated by the rails scaffold -# generator. If you are using any extension libraries to generate different -# controller code, this generated spec may or may not pass. -# -# It only uses APIs available in rails and/or rspec-rails. There are a number -# of tools you can use to make these specs even more expressive, but we're -# sticking to rails and rspec-rails APIs to keep things simple and stable. -# -# Compared to earlier versions of this generator, there is very limited use of -# stubs and message expectations in this spec. Stubs are only used when there -# is no simpler way to get a handle on the object needed for the example. -# Message expectations are only used when there is no simpler way to specify -# that an instance is receiving a specific message. -# -# Also compared to earlier versions of this generator, there are no longer any -# expectations of assigns and templates rendered. These features have been -# removed from Rails core in Rails 5, but can be added back in via the -# `rails-controller-testing` gem. - RSpec.describe V3::StopsController, type: :controller do + describe 'GET #index' do + it 'returns a 200 response with stops connected to published tours' do + create_list(:tour_with_stops, 5, theme: create(:theme), mode: create(:mode)) + Tour.first.update(published: true) + Tour.last.update(published: false) + get :index, params: { tenant: Apartment::Tenant.current } + expect(response.status).to eq(200) + expect(Tour.count).to be > Tour.published.count + json.each do |stop| + expect(Stop.find(stop[:id]).tours.any? { |tour| tour.published }) + end + expect(json.count).to be < Stop.count + end - # This should return the minimal set of attributes required to create a valid - # Stop. As you add validations to Stop, be sure to - # adjust the attributes here as well. - let(:valid_attributes) { - skip('Add a hash of attributes valid for your model') - } - - let(:invalid_attributes) { - skip('Add a hash of attributes invalid for your model') - } + it 'returns a 200 response with no stops when request is authenticated by person with no access' do + create_list(:tour_with_stops, 5, theme: create(:theme), mode: create(:mode)) + Tour.first.update(published: true) if Tour.published.empty? + Tour.last.update(published: false) if Tour.published.count == Tour.count + Tour.last.stops.tours = [] if Tour.last.stops.count > 1 + if Stop.all.all? { |s| s.published } + Stop.last.update(tours: []) + end + user = create(:user) + user.tour_sets = [] + user.tours = [] + signed_cookie(user) + get :index, params: { tenant: Apartment::Tenant.current } + expect(response.status).to eq(200) + expect(Tour.count).to be > Tour.published.count + json.each do |stop| + expect(Stop.find(stop[:id]).tours.any? { |tour| tour.published }) + end + expect(json.count).to be < Stop.count + end - # This should return the minimal set of values that should be in the session - # in order to pass any filters (e.g. authentication) defined in - # StopsController. Be sure to keep this updated too. - let(:valid_session) { {} } + it 'returns a 200 response with stops when request is authenticated by tenant admin and tour is unpublished' do + tour = create(:tour, published: false) + tour.update(published: false) + user = create(:user) + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + signed_cookie(user) + get :index, params: { tenant: tour.tenant } + expect(Stop.count).to be > 1 + expect(response.status).to eq(200) + expect(json.count).to eq(Stop.count) + end - describe 'GET #index' do - it 'returns a success response' do - stop = Stop.create! valid_attributes - get :index, params: {}, session: valid_session - expect(response).to be_success + it 'returns a 200 response when request is authenticated by tour author and tour is unpublished' do + create_list(:tour_with_stops, 5, theme: create(:theme), mode: create(:mode)) + user = create(:user) + user.tour_sets = [] + user.tours << Tour.first + signed_cookie(user) + get :index, params: { tenant: Apartment::Tenant.current } + expect(response.status).to eq(200) + expect(json.count).to eq(Tour.first.stops.count) + expect(json.count).to be < Stop.count end end describe 'GET #show' do - it 'returns a success response' do - stop = Stop.create! valid_attributes - get :show, params: { id: stop.to_param }, session: valid_session - expect(response).to be_success + it 'returns a 200 response that is empty stop' do + tour = create(:tour) + tour.update(published: false) + Stop.all.each { |stop| tour.stops << stop } + # Make sure the stop is only associated with the newly created tour + tour.stops.last.update(tours: [tour]) + get :show, params: { tenant: Apartment::Tenant.current, id: tour.stops.last.id } + expect(response.status).to eq(200) + expect(json[:id]).to eq(tour.stops.last.id.to_s) + expect(attributes[:title]).to be_nil + end + + it 'returns a 200 response and stop when stop is part of published tour' do + tour = create(:tour) + tour.update(published: true) + Stop.all.each { |stop| tour.stops << stop } + tour.stops.last.update(tours: [tour]) + get :show, params: { tenant: Apartment::Tenant.current, id: tour.stops.last.id } + expect(response.status).to eq(200) + expect(attributes[:title]).to eq(tour.stops.last.title) + end + + it 'returns a 200 response that is empty stop when request is authenticated by someone w/o permission' do + tour = create(:tour) + tour.update(published: false) + Stop.all.each { |stop| tour.stops << stop } + tour.stops.last.update(tours: [tour]) + user = create(:user) + user.update(super: false) + user.tours = [] + user.tour_sets = [] + signed_cookie(user) + get :show, params: { tenant: Apartment::Tenant.current, id: tour.stops.last.id } + expect(response.status).to eq(200) + expect(json[:id]).to eq(tour.stops.last.id.to_s) + expect(attributes[:title]).to be_nil + end + + it 'returns a 200 response that is a stop when request is authenticated by a tour author' do + tour = create(:tour) + tour.update(published: false) + Stop.all.each { |stop| tour.stops << stop } + tour.stops.first.update(tours: [tour]) + user = create(:user) + user.update(super: false) + user.tours << tour + user.tour_sets = [] + signed_cookie(user) + get :show, params: { tenant: Apartment::Tenant.current, id: tour.stops.first.id } + expect(response.status).to eq(200) + expect(attributes[:title]).to eq(tour.stops.first.title) + end + + it 'returns a 200 response that is a stop when request is authenticated by a tenant admin' do + tour = create(:tour) + tour.update(published: false) + Stop.all.each { |stop| tour.stops << stop } + tour.stops.first.update(tours: [tour]) + user = create(:user) + user.update(super: false) + user.tours = [] + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + signed_cookie(user) + get :show, params: { tenant: Apartment::Tenant.current, id: tour.stops.first.id } + expect(response.status).to eq(200) + expect(attributes[:title]).to eq(tour.stops.first.title) + end + + it 'returns a 200 response that is a stop when request is authenticated by a super user' do + tour = create(:tour) + tour.update(published: false) + Stop.all.each { |stop| tour.stops << stop } + tour.stops.first.update(tours: [tour]) + user = create(:user) + user.update(super: true) + user.tours = [] + user.tour_sets = [] + signed_cookie(user) + get :show, params: { tenant: Apartment::Tenant.current, id: tour.stops.first.id } + expect(response.status).to eq(200) + expect(attributes[:title]).to eq(tour.stops.first.title) end end describe 'POST #create' do context 'with valid params' do - it 'creates a new Stop' do - expect { - post :create, params: { stop: valid_attributes }, session: valid_session - }.to change(Stop, :count).by(1) - end + it 'return 401 when unauthenciated' do + post :create, params: { data: { type: 'stops', attributes: { title: 'Burrito Stop' } }, tenant: Apartment::Tenant.current } + expect(response.status).to eq(401) + end - it 'renders a JSON response with the new stop' do + it 'return 401 when authenciated but not an admin for current tenant' do + user = create(:user) + user.update(super: false) + user.tour_sets = [] + user.tours = [] + signed_cookie(user) + post :create, params: { data: { type: 'stops', attributes: { title: 'Burrito Stop' } }, tenant: Apartment::Tenant.current } + expect(response.status).to eq(401) + end - post :create, params: { stop: valid_attributes }, session: valid_session - expect(response).to have_http_status(:created) - expect(response.content_type).to eq('application/json') - # expect(response.location).to eq(stop_url(Stop.last)) + it 'return 201 when authenciated but an admin for current tenant' do + user = create(:user) + user.update(super: false) + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + signed_cookie(user) + original_stop_count = Stop.count + post :create, params: { data: { type: 'stops', attributes: { title: 'Burrito Stop' } }, tenant: Apartment::Tenant.current } + expect(response.status).to eq(201) + expect(attributes[:title]).to eq('Burrito Stop') + expect(Stop.count).to eq(original_stop_count + 1) end - end - context 'with invalid params' do - it 'renders a JSON response with errors for the new stop' do + it 'return 201 when authenciated by super' do + user = create(:user) + user.tour_sets = [] + user.update(super: true) + signed_cookie(user) + original_stop_count = Stop.count + post :create, params: { data: { type: 'stops', attributes: { title: 'Taco Stop' } }, tenant: Apartment::Tenant.current } + expect(response.status).to eq(201) + expect(attributes[:title]).to eq('Taco Stop') + expect(Stop.count).to eq(original_stop_count + 1) + end - post :create, params: { stop: invalid_attributes }, session: valid_session - expect(response).to have_http_status(:unprocessable_entity) - expect(response.content_type).to eq('application/json') + it 'return 201 when authenciated by a tour author' do + user = create(:user) + user.tour_sets = [] + user.tours << Tour.last + user.update(super: false) + signed_cookie(user) + original_stop_count = Stop.count + post :create, params: { data: { type: 'stops', attributes: { title: 'Elmyr' } }, tenant: Apartment::Tenant.current } + expect(response.status).to eq(201) + expect(attributes[:title]).to eq('Elmyr') + expect(Stop.count).to eq(original_stop_count + 1) end end end describe 'PUT #update' do context 'with valid params' do - let(:new_attributes) { - skip('Add a hash of attributes valid for your model') - } - - it 'updates the requested stop' do - stop = Stop.create! valid_attributes - put :update, params: { id: stop.to_param, stop: new_attributes }, session: valid_session - stop.reload - skip('Add assertions for updated state') + it 'return 401 when unauthenciated' do + create(:tour) + post :update, params: { id: Stop.last.id, data: { type: 'stops', attributes: { title: 'Burrito Stop' } }, tenant: Apartment::Tenant.current } + expect(response.status).to eq(401) end - it 'renders a JSON response with the stop' do - stop = Stop.create! valid_attributes + it 'return 401 when authenciated but not an admin for current tenant' do + create(:tour, published: false) + user = create(:user) + user.update(super: false) + user.tour_sets = [] + user.tours = [] + signed_cookie(user) + post :update, params: { id: Stop.first.id, data: { type: 'stops', attributes: { title: 'Burrito Stop' } }, tenant: Apartment::Tenant.current } + expect(response.status).to eq(401) + end - put :update, params: { id: stop.to_param, stop: valid_attributes }, session: valid_session - expect(response).to have_http_status(:ok) - expect(response.content_type).to eq('application/json') + it 'return 200 and updated tour when authenciated but an admin for current tenant' do + create(:tour, published: false) + user = create(:user) + user.update(super: false) + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + signed_cookie(user) + original_stop_title = Stop.last.title + new_title = Faker::Name.unique.name + post :update, params: { id: Stop.first.id, data: { type: 'stops', attributes: { title: new_title } }, tenant: Apartment::Tenant.current } + expect(response.status).to eq(200) + expect(attributes[:title]).not_to eq(original_stop_title) + expect(attributes[:title]).to eq(new_title) + expect(Stop.first.title).to eq(new_title) end - end - context 'with invalid params' do - it 'renders a JSON response with errors for the stop' do - stop = Stop.create! valid_attributes + it 'return 200 and updated tour when authenciated by super' do + tour = create(:tour) + tour.stops << create_list(:stop, 4) + user = create(:user) + user.tour_sets = [] + user.update(super: true) + signed_cookie(user) + original_stop_title = Stop.last.title + new_title = Faker::Name.unique.name + post :update, params: { id: Stop.last.id, data: { type: 'stops', attributes: { title: new_title } }, tenant: Apartment::Tenant.current } + expect(response.status).to eq(200) + expect(attributes[:title]).not_to eq(original_stop_title) + expect(attributes[:title]).to eq(new_title) + expect(Stop.last.title).to eq(new_title) + end - put :update, params: { id: stop.to_param, stop: invalid_attributes }, session: valid_session - expect(response).to have_http_status(:unprocessable_entity) - expect(response.content_type).to eq('application/json') + it 'return 200 and updated tour when authenciated by tour author' do + tour = create(:tour) + user = create(:user) + user.update(super: false) + user.tour_sets = [] + user.tours << tour + signed_cookie(user) + original_stop_title = Stop.last.title + new_title = Faker::Name.unique.name + post :update, params: { id: Stop.first.id, data: { type: 'stops', attributes: { title: new_title } }, tenant: Apartment::Tenant.current } + expect(response.status).to eq(200) + expect(attributes[:title]).not_to eq(original_stop_title) + expect(attributes[:title]).to eq(new_title) + expect(Stop.first.title).to eq(new_title) end end + + # context 'with invalid params' do + # it 'renders a JSON response with errors for the tour' do + # tour = Stop.create! valid_attributes + + # put :update, params: { id: tour.to_param, tour: invalid_attributes } + # expect(response).to have_http_status(:unprocessable_entity) + # expect(response.content_type).to eq('application/json') + # end + # end end describe 'DELETE #destroy' do - it 'destroys the requested stop' do - stop = Stop.create! valid_attributes - expect { - delete :destroy, params: { id: stop.to_param }, session: valid_session - }.to change(Stop, :count).by(-1) + it 'return 401 when unauthenciated' do + create(:tour) + post :destroy, params: { id: Stop.first.id, tenant: Apartment::Tenant.current } + expect(response.status).to eq(401) end - end + it 'return 401 when authenciated but not an admin for current tenant' do + tour = create(:tour) + user = create(:user) + user.update(super: false) + user.tour_sets = [] + signed_cookie(user) + post :destroy, params: { id: Stop.first.id, tenant: Apartment::Tenant.current } + expect(response.status).to eq(401) + end + + it 'return 204 and one less tour when authenciated but an admin for current tenant' do + tour = create(:tour) + user = create(:user) + user.update(super: false) + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + signed_cookie(user) + stop_count = Stop.count + Stop.last.update(tours: []) + post :destroy, params: { id: Stop.last.id, tenant: Apartment::Tenant.current } + Stop.last.update(tours: []) + expect(response.status).to eq(204) + expect(Stop.count).to eq(stop_count - 1) + end + + it 'return 204 and one less tour when authenciated by super' do + tour = create(:tour) + user = create(:user) + user.tour_sets = [] + user.update(super: true) + signed_cookie(user) + Stop.first.update(tours: []) + stop_count = Stop.count + post :destroy, params: { id: Stop.first.id, tenant: Apartment::Tenant.current } + expect(response.status).to eq(204) + expect(Stop.count).to eq(stop_count - 1) + end + + it 'return 204 and one less stop when authenciated by tour author and Stop does not belong to a Tour' do + tour = create(:tour) + user = create(:user) + user.update(super: false) + user.tour_sets = [] + user.tours << tour + signed_cookie(user) + Stop.last.update(tours: []) + stop_count = Stop.count + post :destroy, params: { id: Stop.last.id, tenant: Apartment::Tenant.current } + expect(response.status).to eq(204) + expect(Stop.count).to eq(stop_count - 1) + end + + it 'return 405 and does not delete Stop when Stop belongs to a Tour and requested by super' do + tour = create(:tour) + user = create(:user) + user.update(super: true) + Stop.last.tours << tour if Stop.last.tours.empty? + signed_cookie(user) + stop_count = Stop.count + post :destroy, params: { id: Stop.last.id, tenant: Apartment::Tenant.current } + expect(response.status).to eq(405) + expect(Stop.count).to eq(stop_count) + end + end end diff --git a/spec/controllers/v3/stops_media_controller_spec-fix.rb b/spec/controllers/v3/stops_media_controller_spec-fix.rb deleted file mode 100644 index 52ee1c5f..00000000 --- a/spec/controllers/v3/stops_media_controller_spec-fix.rb +++ /dev/null @@ -1,6 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' -RSpec.describe V3::StopsMediaController, type: :controller do - -end diff --git a/spec/controllers/v3/stops_media_controller_spec.rb b/spec/controllers/v3/stops_media_controller_spec.rb new file mode 100644 index 00000000..932c344d --- /dev/null +++ b/spec/controllers/v3/stops_media_controller_spec.rb @@ -0,0 +1,189 @@ +require 'rails_helper' + +RSpec.describe V3::StopMediaController, type: :controller do + + describe 'GET #index' do + before(:each) { Tour.all.each { |tour| tour.update(published: false) } } + + context 'unauthenticated' do + it 'returns a success response but zero StopMedium objects' do + create(:stop_medium, stop: create(:stop)) + get :index, params: { tenant: Apartment::Tenant.current } + expect(response.status).to eq(200) + expect(json.count).to eq(0) + expect(StopMedium.count).to be > 0 + end + end + + context 'authenticated unauthorized' do + it 'returns zero StopMedium objects, not current tenant admin, non tour author' do + original_tenant = Apartment::Tenant.current + stop_medium = create(:stop_medium, stop: create(:stop)) + tour_set = create(:tour_set) + user = create(:user, super: false) + user.tour_sets << tour_set + signed_cookie(user) + get :index, params: { tenant: original_tenant } + Apartment::Tenant.switch! original_tenant + expect(response.status).to eq(200) + expect(json.count).to eq(0) + expect(StopMedium.count).to be > 0 + end + + it 'returns zero StopMedium objects, not current tenant admin, non tour author' do + original_tenant = Apartment::Tenant.current + stop_medium = create(:stop_medium, stop: create(:stop)) + tour_set = create(:tour_set) + Apartment::Tenant.switch! tour_set.subdir + user = create(:user, super: false) + user.tours << create(:tour) + signed_cookie(user) + Apartment::Tenant.switch! original_tenant + get :index, params: { tenant: original_tenant } + expect(response.status).to eq(200) + expect(json.count).to eq(0) + expect(StopMedium.count).to be > 0 + end + end + + context 'authenticated and authorized' do + it 'returns all StopMedium objects to super' do + create_list(:stop_medium, 4) + user = create(:user, super: true) + signed_cookie(user) + get :index, params: { tenant: Apartment::Tenant.current } + expect(response.status).to eq(200) + expect(json.count).to eq(StopMedium.count) + end + + it 'returns all StopMedium objects to tenant admin' do + create_list(:stop_medium, 4) + user = create(:user, super: false) + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + signed_cookie(user) + get :index, params: { tenant: Apartment::Tenant.current } + expect(response.status).to eq(200) + expect(json.count).to eq(StopMedium.count) + end + end + end + + describe 'GET #show' do + context 'unauthenticated' do + it 'returns a success response but empty StopMedium objects' do + + stop_medium = create(:stop_medium, stop: create(:stop)) + get :show, params: { id: stop_medium.id, tenant: Apartment::Tenant.current } + expect(response.status).to eq(200) + expect(relationships[:medium][:data]).to be nil + expect(relationships[:stop][:data]).to be nil + expect(stop_medium.stop).not_to be nil + expect(stop_medium.medium).not_to be nil + expect(StopMedium.count).to be > 0 + end + + # it 'returns a success response but empty StopMedium objects' do + # stop_medium = create(:stop_medium, stop: create(:stop)) + # get :index, params: { id: stop_medium.id, tenant: Apartment::Tenant.current } + # expect(response.status).to eq(200) + # expect(json.count).to eq(1) + # end + end + + context 'authenticated unauthorized' do + + it 'returns empty StopMedium objects, not current tenant admin, non tour author' do + original_tenant = Apartment::Tenant.current + stop_medium = create(:stop_medium, stop: create(:stop)) + tour_set = create(:tour_set) + user = create(:user, super: false) + user.tour_sets << tour_set + signed_cookie(user) + Apartment::Tenant.switch! original_tenant + get :show, params: { id: stop_medium.id, tenant: original_tenant } + expect(response.status).to eq(200) + expect(relationships[:medium][:data]).to be nil + expect(relationships[:stop][:data]).to be nil + expect(stop_medium.stop).not_to be nil + expect(stop_medium.medium).not_to be nil + expect(StopMedium.count).to be > 0 + end + + it 'returns empty StopMedium objects, not current tenant admin, non tour author' do + original_tenant = Apartment::Tenant.current + stop_medium = create(:stop_medium, stop: create(:stop)) + tour_set = create(:tour_set) + Apartment::Tenant.switch! tour_set.subdir + user = create(:user, super: false) + user.tours << create(:tour) + signed_cookie(user) + Apartment::Tenant.switch! original_tenant + get :show, params: { id: stop_medium.id, tenant: original_tenant } + expect(response.status).to eq(200) + expect(relationships[:medium][:data]).to be nil + expect(relationships[:stop][:data]).to be nil + expect(StopMedium.count).to be > 0 + end + end + + context 'authenticated and authorized' do + it 'returns all StopMedium objects to super' do + create_list(:stop_medium, 4) + user = create(:user, super: true) + signed_cookie(user) + get :show, params: { id: StopMedium.last.id, tenant: Apartment::Tenant.current } + expect(response.status).to eq(200) + expect(json.count).to eq(StopMedium.count) + end + end + end + + describe 'POST #create' do + it 'returns does not create a new StopMedium' do + expect { + post :create, params: { tenant: Apartment::Tenant.current } + }.to change(StopMedium, :count).by(0) + end + + it 'returns 401' do + user = create(:user, super: true) + signed_cookie(user) + post :create, params: { data: { type: 'stop_media', attributes: { stop_id: 1, medium_id: 1, position: 1 } }, tenant: Apartment::Tenant.current } + expect(response.status).to eq(401) + end + end + + describe 'PUT #update' do + context 'with valid params' do + it 'renders a JSON response with the v3_stop_medium' do + stop_medium = create(:stop_medium, position: 1) + expect(stop_medium.position).not_to eq(100) + user = create(:user, super: true) + signed_cookie(user) + put :update, params: { id: stop_medium.id, data: { type: 'stop_media', attributes: { stop_id: stop_medium.stop.id, medium_id: stop_medium.medium.id, position: 100 } }, tenant: Apartment::Tenant.current } + expect(response).to have_http_status(:ok) + expect(attributes[:position]).to eq(100) + expect(StopMedium.find(stop_medium.id).position).to eq(100) + end + end + end + + describe 'DELETE #destroy' do + it 'does not destroy the requested v3_stop_medium' do + stop_medium = create(:stop_medium) + user = create(:user, super: true) + signed_cookie(user) + expect { + delete :destroy, params: { id: stop_medium.to_param, tenant: Apartment::Tenant.current } + }.to change(StopMedium, :count).by(0) + end + + it 'responds with 405' do + stop_medium = create(:stop_medium) + user = create(:user, super: true) + signed_cookie(user) + delete :destroy, params: { id: stop_medium.to_param, tenant: Apartment::Tenant.current } + expect(response.status).to eq(405) + end + end +end diff --git a/spec/controllers/v3/themes_controller_spec.rb b/spec/controllers/v3/themes_controller_spec.rb index 7d06fb67..80557559 100644 --- a/spec/controllers/v3/themes_controller_spec.rb +++ b/spec/controllers/v3/themes_controller_spec.rb @@ -2,129 +2,42 @@ require 'rails_helper' -# This spec was generated by rspec-rails when you ran the scaffold generator. -# It demonstrates how one might use RSpec to specify the controller code that -# was generated by Rails when you ran the scaffold generator. -# -# It assumes that the implementation code is generated by the rails scaffold -# generator. If you are using any extension libraries to generate different -# controller code, this generated spec may or may not pass. -# -# It only uses APIs available in rails and/or rspec-rails. There are a number -# of tools you can use to make these specs even more expressive, but we're -# sticking to rails and rspec-rails APIs to keep things simple and stable. -# -# Compared to earlier versions of this generator, there is very limited use of -# stubs and message expectations in this spec. Stubs are only used when there -# is no simpler way to get a handle on the object needed for the example. -# Message expectations are only used when there is no simpler way to specify -# that an instance is receiving a specific message. -# -# Also compared to earlier versions of this generator, there are no longer any -# expectations of assigns and templates rendered. These features have been -# removed from Rails core in Rails 5, but can be added back in via the -# `rails-controller-testing` gem. - RSpec.describe V3::ThemesController, type: :controller do - # This should return the minimal set of attributes required to create a valid - # Theme. As you add validations to Theme, be sure to - # adjust the attributes here as well. - let(:valid_attributes) { - skip('Add a hash of attributes valid for your model') - } - - let(:invalid_attributes) { - skip('Add a hash of attributes invalid for your model') - } - - # This should return the minimal set of values that should be in the session - # in order to pass any filters (e.g. authentication) defined in - # ThemesController. Be sure to keep this updated too. - let(:valid_session) { {} } + before(:each) { create_list(:theme, rand(3..6)) } describe 'GET #index' do it 'returns a success response' do - theme = Theme.create! valid_attributes - get :index, params: {}, session: valid_session - expect(response).to be_success + get :index, params: { tenant: Apartment::Tenant.current } + expect(response.status).to eq(200) end end describe 'GET #show' do it 'returns a success response' do - theme = Theme.create! valid_attributes - get :show, params: { id: theme.to_param }, session: valid_session - expect(response).to be_success + get :show, params: { id: Theme.last.to_param, tenant: Apartment::Tenant.current } + expect(response.status).to eq(200) end end describe 'POST #create' do - context 'with valid params' do - it 'creates a new Theme' do - expect { - post :create, params: { theme: valid_attributes }, session: valid_session - }.to change(Theme, :count).by(1) - end - - it 'renders a JSON response with the new theme' do - - post :create, params: { theme: valid_attributes }, session: valid_session - expect(response).to have_http_status(:created) - expect(response.content_type).to eq('application/json') - expect(response.location).to eq(theme_url(Theme.last)) - end - end - - context 'with invalid params' do - it 'renders a JSON response with errors for the new theme' do - - post :create, params: { theme: invalid_attributes }, session: valid_session - expect(response).to have_http_status(:unprocessable_entity) - expect(response.content_type).to eq('application/json') - end + it 'returns 405' do + post :create, params: { tenant: Apartment::Tenant.current, data: { type: 'theme', attributes: {} } } + expect(response.status).to eq(405) end end describe 'PUT #update' do - context 'with valid params' do - let(:new_attributes) { - skip('Add a hash of attributes valid for your model') - } - - it 'updates the requested theme' do - theme = Theme.create! valid_attributes - put :update, params: { id: theme.to_param, theme: new_attributes }, session: valid_session - theme.reload - skip('Add assertions for updated state') - end - - it 'renders a JSON response with the theme' do - theme = Theme.create! valid_attributes - - put :update, params: { id: theme.to_param, theme: valid_attributes }, session: valid_session - expect(response).to have_http_status(:ok) - expect(response.content_type).to eq('application/json') - end - end - - context 'with invalid params' do - it 'renders a JSON response with errors for the theme' do - theme = Theme.create! valid_attributes - - put :update, params: { id: theme.to_param, theme: invalid_attributes }, session: valid_session - expect(response).to have_http_status(:unprocessable_entity) - expect(response.content_type).to eq('application/json') - end + it 'renders a JSON response with errors for the theme' do + put :update, params: { id: Theme.first.to_param, tenant: Apartment::Tenant.current, data: { type: 'theme', attributes: {} } } + expect(response.status).to eq(405) end end describe 'DELETE #destroy' do it 'destroys the requested theme' do - theme = Theme.create! valid_attributes - expect { - delete :destroy, params: { id: theme.to_param }, session: valid_session - }.to change(Theme, :count).by(-1) + delete :destroy, params: { id: Theme.first.to_param, tenant: Apartment::Tenant.current } + expect(response.status).to eq(405) end end diff --git a/spec/controllers/v3/tour_authors_controller_spec.rb b/spec/controllers/v3/tour_authors_controller_spec.rb new file mode 100755 index 00000000..c0931e9f --- /dev/null +++ b/spec/controllers/v3/tour_authors_controller_spec.rb @@ -0,0 +1,123 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe V3::TourAuthorsController, type: :controller do + before(:each) { + create_list(:tour_set, rand(2..5)) + TourSet.all.each { |tour_set| tour_set.update(admins: create_list(:user, rand(2..5))) } + } + + describe 'GET #index' do + context 'unauthenticated and unauthorized' do + it 'returns 401 when not unauthenticated' do + get :index, params: { tenant: TourSet.first.subdir } + expect(response.status).to eq(401) + end + + it 'returns 401 when authenticated but unauthorized' do + initial_tour_set = TourSet.find_by(subdir: Apartment::Tenant.current) + user = create(:user, super: false) + user.tour_sets << create(:tour_set) + expect(user.tour_sets).not_to include initial_tour_set + signed_cookie(user) + Apartment::Tenant.switch! initial_tour_set.subdir + get :index, params: { tenant: initial_tour_set.subdir } + expect(response.status).to eq(401) + end + end + + context 'authorized' do + it 'responds with 200 and a list of TourAuthors when requested by tenant admin' do + create_list(:tour_author, rand(3..4)) + user = create(:user, super: false) + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + signed_cookie(user) + get :index, params: { tenant: Apartment::Tenant.current } + expect(response.status).to eq(200) + expect(json.first[:type]).to eq('tour_authors') + expect(TourAuthor.count).to be > 1 + expect(json.count).to eq(TourAuthor.count) + end + + it 'responds with 200 and a list of TourAuthors when requested by super' do + create_list(:tour_author, rand(4..6)) + user = create(:user, super: true) + signed_cookie(user) + get :index, params: { tenant: Apartment::Tenant.current } + expect(response.status).to eq(200) + expect(json.first[:type]).to eq('tour_authors') + expect(TourAuthor.count).to be > 1 + expect(json.count).to eq(TourAuthor.count) + end + end + end + + describe 'GET #show' do + context 'unauthorized' do + it 'returns 401 when unauthenticated' do + tour_author = create(:tour_author) + get :show, params: { tenant: Apartment::Tenant.current, id: tour_author.id } + expect(response.status).to eq(401) + end + + it 'returns 401 when authenticated but not authorized' do + tour_author = create(:tour_author) + initial_tour_set = TourSet.find_by(subdir: Apartment::Tenant.current) + user = create(:user, super: false) + user.tour_sets << create(:tour_set) + expect(user.tour_sets).not_to include initial_tour_set + signed_cookie(user) + Apartment::Tenant.switch! initial_tour_set.subdir + get :show, params: { tenant: Apartment::Tenant.current, id: tour_author.id } + expect(response.status).to eq(401) + end + end + + context 'authorized' do + it 'responds with 200 and a list of TourAuthors when requested by tenant admin' do + tour_author = create(:tour_author) + user = create(:user, super: false) + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + signed_cookie(user) + get :show, params: { tenant: Apartment::Tenant.current, id: tour_author.id } + expect(response.status).to eq(200) + expect(json[:type]).to eq('tour_authors') + expect(json[:id]).to eq(tour_author.id.to_s) + end + + it 'responds with 200 and a list of TourAuthors when requested by super' do + tour_author = create(:tour_author) + user = create(:user, super: true) + signed_cookie(user) + get :show, params: { tenant: Apartment::Tenant.current, id: tour_author.id } + expect(response.status).to eq(200) + expect(json[:type]).to eq('tour_authors') + expect(json[:id]).to eq(tour_author.id.to_s) + end + end + end + + describe 'POST #create' do + it 'returns 405' do + post :create, params: { tenant: Apartment::Tenant.current, data: {} } + expect(response.status).to eq(405) + end + end + + describe 'PUT #update' do + it 'returns 405' do + tour_author = create(:tour_author) + put :update, params: { tenant: Apartment::Tenant.current, id: tour_author.id } + expect(response.status).to eq(405) + end + end + + describe 'DELETE #destroy' do + it 'returns 405' do + tour_author = create(:tour_author) + delete :destroy, params: { tenant: Apartment::Tenant.current, id: tour_author.id } + expect(response.status).to eq(405) + end + end +end diff --git a/spec/controllers/v3/tour_collections_controller_spec.rb b/spec/controllers/v3/tour_collections_controller_spec.rb deleted file mode 100644 index 6d53c22a..00000000 --- a/spec/controllers/v3/tour_collections_controller_spec.rb +++ /dev/null @@ -1,131 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -# This spec was generated by rspec-rails when you ran the scaffold generator. -# It demonstrates how one might use RSpec to specify the controller code that -# was generated by Rails when you ran the scaffold generator. -# -# It assumes that the implementation code is generated by the rails scaffold -# generator. If you are using any extension libraries to generate different -# controller code, this generated spec may or may not pass. -# -# It only uses APIs available in rails and/or rspec-rails. There are a number -# of tools you can use to make these specs even more expressive, but we're -# sticking to rails and rspec-rails APIs to keep things simple and stable. -# -# Compared to earlier versions of this generator, there is very limited use of -# stubs and message expectations in this spec. Stubs are only used when there -# is no simpler way to get a handle on the object needed for the example. -# Message expectations are only used when there is no simpler way to specify -# that an instance is receiving a specific message. -# -# Also compared to earlier versions of this generator, there are no longer any -# expectations of assigns and templates rendered. These features have been -# removed from Rails core in Rails 5, but can be added back in via the -# `rails-controller-testing` gem. - -RSpec.describe V3::TourCollectionsController, type: :controller do - - # This should return the minimal set of attributes required to create a valid - # TourCollection. As you add validations to TourCollection, be sure to - # adjust the attributes here as well. - let(:valid_attributes) { - skip('Add a hash of attributes valid for your model') - } - - let(:invalid_attributes) { - skip('Add a hash of attributes invalid for your model') - } - - # This should return the minimal set of values that should be in the session - # in order to pass any filters (e.g. authentication) defined in - # TourCollectionsController. Be sure to keep this updated too. - let(:valid_session) { {} } - - describe 'GET #index' do - it 'returns a success response' do - tour_collection = TourCollection.create! valid_attributes - get :index, params: {}, session: valid_session - expect(response).to be_success - end - end - - describe 'GET #show' do - it 'returns a success response' do - tour_collection = TourCollection.create! valid_attributes - get :show, params: { id: tour_collection.to_param }, session: valid_session - expect(response).to be_success - end - end - - describe 'POST #create' do - context 'with valid params' do - it 'creates a new TourCollection' do - expect { - post :create, params: { v3_tour_collection: valid_attributes }, session: valid_session - }.to change(TourCollection, :count).by(1) - end - - it 'renders a JSON response with the new v3_tour_collection' do - - post :create, params: { v3_tour_collection: valid_attributes }, session: valid_session - expect(response).to have_http_status(:created) - expect(response.content_type).to eq('application/json') - expect(response.location).to eq(v3_tour_collection_url(TourCollection.last)) - end - end - - context 'with invalid params' do - it 'renders a JSON response with errors for the new v3_tour_collection' do - - post :create, params: { v3_tour_collection: invalid_attributes }, session: valid_session - expect(response).to have_http_status(:unprocessable_entity) - expect(response.content_type).to eq('application/json') - end - end - end - - describe 'PUT #update' do - context 'with valid params' do - let(:new_attributes) { - skip('Add a hash of attributes valid for your model') - } - - it 'updates the requested v3_tour_collection' do - tour_collection = TourCollection.create! valid_attributes - put :update, params: { id: tour_collection.to_param, v3_tour_collection: new_attributes }, session: valid_session - tour_collection.reload - skip('Add assertions for updated state') - end - - it 'renders a JSON response with the v3_tour_collection' do - tour_collection = TourCollection.create! valid_attributes - - put :update, params: { id: tour_collection.to_param, v3_tour_collection: valid_attributes }, session: valid_session - expect(response).to have_http_status(:ok) - expect(response.content_type).to eq('application/json') - end - end - - context 'with invalid params' do - it 'renders a JSON response with errors for the v3_tour_collection' do - tour_collection = TourCollection.create! valid_attributes - - put :update, params: { id: tour_collection.to_param, v3_tour_collection: invalid_attributes }, session: valid_session - expect(response).to have_http_status(:unprocessable_entity) - expect(response.content_type).to eq('application/json') - end - end - end - - describe 'DELETE #destroy' do - it 'destroys the requested v3_tour_collection' do - tour_collection = TourCollection.create! valid_attributes - expect { - delete :destroy, params: { id: tour_collection.to_param }, session: valid_session - }.to change(TourCollection, :count).by(-1) - end - end - -end diff --git a/spec/controllers/v3/tour_flat_pages_controller_spec.rb b/spec/controllers/v3/tour_flat_pages_controller_spec.rb new file mode 100755 index 00000000..5043ef0d --- /dev/null +++ b/spec/controllers/v3/tour_flat_pages_controller_spec.rb @@ -0,0 +1,365 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe V3::TourFlatPagesController, type: :controller do + def data(tour, flat_page, position = 1) + { + type: 'tour_flat_pages', + attributes: { position: position }, + relationships: { + tour: { data: { type: 'tours', id: tour.id } }, + flat_page: { data: { type: 'flat_pages', id: flat_page.id } } + } + } + end + + describe 'GET #index' do + it 'returns a 200 response and empty tour when none are part of a published tour' do + Tour.all.each { |tour| tour.update(published: false) } + get :index, params: { tenant: Apartment::Tenant.current } + expect(json).to be_empty + expect(response.status).to eq(200) + end + + it 'returns a 200 response and only tour flat_pages that are part of a published tour' do + create_list(:tour_with_flat_pages, 5, theme: create(:theme), mode: create(:mode)) + Tour.first.update(published: true) if Tour.published.empty? + Tour.last.update(published: false) if Tour.published.count == Tour.count + get :index, params: { tenant: Apartment::Tenant.current } + expect(response.status).to eq(200) + expect(json.count).to eq(Tour.published.map { |tour| tour.tour_flat_pages.count }.sum) + end + + it 'returns a 200 response when requeted by slug' do + tour = create(:tour_with_flat_pages) + tour.update(published: true) + get :index, params: { tenant: Apartment::Tenant.current } + expect(response.status).to eq(200) + expect(json.count).to eq(FlatPage.count) + end + + it 'returns a 200 response when request is authenticated by tenant admin and tour is unpublished' do + tour = create(:tour_with_flat_pages, published: false) + tour.update(published: false) + user = create(:user) + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + signed_cookie(user) + get :index, params: { tenant: Apartment::Tenant.current } + expect(response.status).to eq(200) + expect(json.count).to eq(FlatPage.count) + end + + it 'returns a 200 response when request is authenticated by tour author and tour is unpublished' do + tour = create(:tour_with_flat_pages, published: false) + tour.update(published: false) + user = create(:user) + user.tour_sets = [] + user.tours << tour + signed_cookie(user) + get :index, params: { tenant: Apartment::Tenant.current } + expect(response.status).to eq(200) + expect(json.count).to eq(FlatPage.count) + end + end + + describe 'GET #show' do + it 'returns a 200 response' do + tour = create(:tour_with_flat_pages) + tour.update(published: true) + get :show, params: { tenant: Apartment::Tenant.current, id: tour.tour_flat_pages.first.id } + expect(response.status).to eq(200) + expect(relationships[:tour][:data][:id]).to eq(tour.id.to_s) + end + + it 'returns a 200 response when request is authenticated by tour author and tour is unpublished' do + tour = create(:tour_with_flat_pages) + tour.update(published: false) + user = create(:user) + user.tour_sets = [] + user.tours << tour + signed_cookie(user) + get :show, params: { tenant: Apartment::Tenant.current, id: tour.tour_flat_pages.first.id } + expect(response.status).to eq(200) + expect(relationships[:tour][:data][:id]).to eq(tour.id.to_s) + end + + it 'returns a 200 response when request is authenticated by tenant admin and tour is unpublished' do + tour = create(:tour_with_flat_pages) + tour.update(published: false) + user = create(:user) + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + signed_cookie(user) + get :show, params: { tenant: Apartment::Tenant.current, id: tour.tour_flat_pages.first.id } + expect(response.status).to eq(200) + expect(relationships[:tour][:data][:id]).to eq(tour.id.to_s) + end + + it 'returns a 200 response and empty json when tour is unpublished and request is not authenticated' do + tour = create(:tour_with_flat_pages) + tour.update(published: false) + get :show, params: { tenant: Apartment::Tenant.current, id: tour.tour_flat_pages.first.id } + expect(response.status).to eq(200) + expect(json).to be_empty + end + + it 'returns a 200 response and empty json when tour is unpublished and request is authenticated by someone who is nither a tenant admin or tour author' do + tour = create(:tour_with_flat_pages) + tour.update(published: false) + user = create(:user) + user.tours = [] + user.tour_sets = [] + signed_cookie(user) + get :show, params: { tenant: Apartment::Tenant.current, id: tour.tour_flat_pages.first.id } + expect(response.status).to eq(200) + expect(json).to be_empty + end + end + + # TourFlatPage objects are NOT created via tha API. Every test should return 401 + describe 'POST #create' do + context 'with valid params' do + it 'return 405 when unauthenciated' do + tour = create(:tour) + flat_page = create(:flat_page) + post :create, params: { data: data(tour, flat_page), tenant: TourSet.first.subdir } + expect(response.status).to eq(405) + end + + it 'return 405 when authenciated but not an admin for current tenant' do + tour = create(:tour) + flat_page = create(:flat_page) + original_tour_flat_page_count = TourFlatPage.count + user = create(:user) + user.update(super: false) + user.tour_sets = [] + user.tours = [] + signed_cookie(user) + post :create, params: { data: data(tour, flat_page), tenant: Apartment::Tenant.current } + expect(response.status).to eq(405) + expect(original_tour_flat_page_count).to eq(TourFlatPage.count) + end + + it 'return 405 when authenciated but an admin for current tenant' do + tour = create(:tour) + flat_page = create(:flat_page) + original_tour_flat_page_count = TourFlatPage.count + user = create(:user) + user.update(super: false) + user.tours = [] + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + signed_cookie(user) + post :create, params: { data: data(tour, flat_page), tenant: Apartment::Tenant.current } + expect(response.status).to eq(405) + expect(original_tour_flat_page_count).to eq(TourFlatPage.count) + end + + it 'return 405 when authenciated by super' do + tour = create(:tour) + flat_page = create(:flat_page) + original_tour_flat_page_count = TourFlatPage.count + user = create(:user) + user.tours = [] + user.tour_sets = [] + user.update(super: true) + signed_cookie(user) + post :create, params: { data: data(tour, flat_page), tenant: Apartment::Tenant.current } + expect(response.status).to eq(405) + expect(original_tour_flat_page_count).to eq(TourFlatPage.count) + end + + it 'return 405 when authenciated by tour author' do + tour = create(:tour) + flat_page = create(:flat_page) + original_tour_flat_page_count = TourFlatPage.count + user = create(:user) + user.tours << tour + user.tour_sets = [] + user.update(super: false) + signed_cookie(user) + post :create, params: { data: data(tour, flat_page), tenant: Apartment::Tenant.current } + expect(response.status).to eq(405) + expect(original_tour_flat_page_count).to eq(TourFlatPage.count) + end + end + end + + describe 'PUT #update' do + context 'with valid params' do + it 'return 401 when unauthenciated' do + tour = create(:tour) + flat_page = create(:flat_page) + tour.flat_pages << flat_page + request_data = data(tour, flat_page, 4) + request_data[:id] = TourFlatPage.find_by(tour: tour, flat_page: flat_page).id + post :update, params: { id: request_data[:id], data: request_data, tenant: TourSet.first.subdir } + expect(response.status).to eq(401) + end + + it 'return 401 when authenciated but not an admin for current tenant' do + tour = create(:tour) + flat_page = create(:flat_page) + tour.flat_pages << flat_page + user = create(:user) + user.update(super: false) + user.tour_sets = [] + user.tours = [] + signed_cookie(user) + request_data = data(tour, flat_page, 5) + request_data[:id] = TourFlatPage.find_by(tour: tour, flat_page: flat_page).id + post :update, params: { id: request_data[:id], data: request_data, tenant: TourSet.first.subdir } + expect(response.status).to eq(401) + end + + it 'return 200 and updated tour when authenciated but an admin for current tenant' do + tour = create(:tour) + flat_pages = create_list(:flat_page, 5) + flat_pages.each { |flat_page| tour.flat_pages << flat_page } + tour.save + flat_page = FlatPage.find(flat_pages.first.id) + tour.flat_pages << flat_page + user = create(:user) + user.update(super: false) + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + user.tours = [] + signed_cookie(user) + tour_flat_page = TourFlatPage.find_by(tour: tour, flat_page: flat_page) + tour_flat_page.update(position: 2) + expect(TourFlatPage.find(tour_flat_page.id).position).to eq(2) + request_data = data(tour, flat_page, 5) + request_data[:id] = tour_flat_page.id + post :update, params: { id: tour_flat_page.id, data: request_data, tenant: TourSet.first.subdir } + expect(response.status).to eq(200) + expect(attributes[:position]).not_to eq('5') + expect(TourFlatPage.find(tour_flat_page.id).position).to eq(5) + end + + it 'return 200 and updated tour when authenciated by super' do + tour = create(:tour) + flat_pages = create_list(:flat_page, 5) + flat_pages.each { |flat_page| tour.flat_pages << flat_page } + tour.save + flat_page = FlatPage.find(flat_pages.first.id) + tour.flat_pages << flat_page + user = create(:user) + user.update(super: true) + user.tour_sets = [] + user.tours = [] + signed_cookie(user) + tour_flat_page = TourFlatPage.find_by(tour: tour, flat_page: flat_page) + tour_flat_page.update(position: 3) + expect(TourFlatPage.find(tour_flat_page.id).position).to eq(3) + request_data = data(tour, flat_page, 4) + request_data[:id] = tour_flat_page.id + post :update, params: { id: tour_flat_page.id, data: request_data, tenant: TourSet.first.subdir } + expect(response.status).to eq(200) + expect(attributes[:position]).not_to eq('4') + expect(TourFlatPage.find(tour_flat_page.id).position).to eq(4) + end + + it 'return 200 and updated tour when authenciated by tour author' do + tour = create(:tour) + flat_pages = create_list(:flat_page, 5) + flat_pages.each { |flat_page| tour.flat_pages << flat_page } + tour.save + flat_page = FlatPage.find(flat_pages.first.id) + tour.flat_pages << flat_page + user = create(:user) + user.update(super: false) + user.tour_sets = [] + user.tours << tour + signed_cookie(user) + tour_flat_page = TourFlatPage.find_by(tour: tour, flat_page: flat_page) + tour_flat_page.update(position: 6) + expect(TourFlatPage.find(tour_flat_page.id).position).to eq(6) + request_data = data(tour, flat_page, 1) + request_data[:id] = tour_flat_page.id + post :update, params: { id: tour_flat_page.id, data: request_data, tenant: TourSet.first.subdir } + expect(response.status).to eq(200) + expect(attributes[:position]).not_to eq('1') + expect(TourFlatPage.find(tour_flat_page.id).position).to eq(1) + end + + it 'returns 422 when params are invalid' do + tour_flat_page = create(:tour_flat_page) + user = create(:user, super: true) + invalid_params = { type: 'tour_flat_pages', attributes: {}, relationships: { tour: { data: nil }, flat_page: { data: nil } } } + signed_cookie(user) + post :update, params: { id: tour_flat_page.id, data: invalid_params, tenant: TourSet.first.subdir } + expect(response.status).to eq(422) + expect(errors).to include('Tour must exist') + end + end + end + + describe 'DELETE #destroy' do + it 'return 405 when unauthenciated' do + tour = create(:tour) + flat_page = create(:flat_page) + tour.flat_pages << flat_page + tour_flat_page = TourFlatPage.find_by(tour: tour, flat_page: flat_page) + post :destroy, params: { id: tour_flat_page.id, tenant: TourSet.first.subdir } + expect(response.status).to eq(405) + end + + it 'return 405 when authenciated but not an admin for current tenant' do + tour = create(:tour) + flat_page = create(:flat_page) + tour.flat_pages << flat_page + tour_flat_page = TourFlatPage.find_by(tour: tour, flat_page: flat_page) + user = create(:user) + user.update(super: false) + user.tour_sets = [] + signed_cookie(user) + post :destroy, params: { id: tour_flat_page.id, tenant: TourSet.first.subdir } + expect(response.status).to eq(405) + end + + it 'return 405 and one less tour when authenciated but an admin for current tenant' do + tour = create(:tour) + flat_page = create(:flat_page) + tour.flat_pages << flat_page + tour_flat_page = TourFlatPage.find_by(tour: tour, flat_page: flat_page) + user = create(:user) + user.update(super: false) + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + signed_cookie(user) + tour_count = Tour.count + post :destroy, params: { id: tour_flat_page.id, tenant: Apartment::Tenant.current } + expect(response.status).to eq(405) + expect(Tour.count).to eq(tour_count) + end + + it 'return 405 and one less tour when authenciated by super' do + tour = create(:tour) + flat_page = create(:flat_page) + tour.flat_pages << flat_page + tour_flat_page = TourFlatPage.find_by(tour: tour, flat_page: flat_page) + user = create(:user) + user.tour_sets = [] + user.update(super: true) + signed_cookie(user) + tour_count = Tour.count + post :destroy, params: { id: tour_flat_page.id, tenant: Apartment::Tenant.current } + expect(response.status).to eq(405) + expect(Tour.count).to eq(tour_count) + end + + it 'return 405 and one less tour when authenciated by tour author' do + tour = create(:tour) + flat_page = create(:flat_page) + tour.flat_pages << flat_page + tour_flat_page = TourFlatPage.find_by(tour: tour, flat_page: flat_page) + user = create(:user) + user.update(super: false) + user.tour_sets = [] + user.tours << tour + signed_cookie(user) + new_title = Faker::Name.unique.name + tour_count = Tour.count + post :destroy, params: { id: tour_flat_page.id, tenant: Apartment::Tenant.current } + expect(response.status).to eq(405) + expect(Tour.count).to eq(tour_count) + end + end +end diff --git a/spec/controllers/v3/tour_geojson_controller_spec.rb b/spec/controllers/v3/tour_geojson_controller_spec.rb index af122ccc..b3193615 100644 --- a/spec/controllers/v3/tour_geojson_controller_spec.rb +++ b/spec/controllers/v3/tour_geojson_controller_spec.rb @@ -2,4 +2,25 @@ RSpec.describe V3::GeojsonToursController, type: :controller do + describe 'GET #show' do + it 'returns a geojosn representation of a tour when tour published' do + tour = create(:tour, published: true, media: create_list(:medium, rand(1..3)), stops: create_list(:stop, rand(4..7))) + tour.stops.each { |stop| stop.media << create_list(:medium, rand(1..3)) } + get :show, params: { id: tour.to_param, tenant: Apartment::Tenant.current } + geojson = JSON.parse(response.body).with_indifferent_access + first_stop = Stop.find_by(title: geojson[:features].first[:properties][:title]) + expect(geojson[:type]).to eq('FeatureCollection') + expect(geojson[:features].count).to eq(tour.stops.count) + expect(geojson[:features].first[:geometry][:coordinates]).to eq([first_stop.lng.to_f, first_stop.lat.to_f]) + expect(first_stop.media.map(&:caption)).to include geojson[:features].first[:properties][:images].first[:caption] + end + + # TODO: Renable after OpenWorld stuff + # it 'returns 401 when tour is unpublished' do + # tour = create(:tour, published: false) + # get :show, params: { id: tour.to_param, tenant: Apartment::Tenant.current } + # expect(response.status).to eq(401) + # end + end + end diff --git a/spec/controllers/v3/tour_media_controller_spec.rb b/spec/controllers/v3/tour_media_controller_spec.rb index 35b29e2d..ac53ccde 100644 --- a/spec/controllers/v3/tour_media_controller_spec.rb +++ b/spec/controllers/v3/tour_media_controller_spec.rb @@ -25,105 +25,218 @@ RSpec.describe V3::TourMediaController, type: :controller do - # This should return the minimal set of attributes required to create a valid - # TourMedium. As you add validations to TourMedium, be sure to - # adjust the attributes here as well. - let(:valid_attributes) { - skip("Add a hash of attributes valid for your model") - } - - let(:invalid_attributes) { - skip("Add a hash of attributes invalid for your model") - } - - # This should return the minimal set of values that should be in the session - # in order to pass any filters (e.g. authentication) defined in - # TourMediaController. Be sure to keep this updated too. - let(:valid_session) { {} } - - describe "GET #index" do - it "returns a success response" do - tour_medium = TourMedium.create! valid_attributes - get :index, params: {}, session: valid_session - expect(response).to be_success - end - end + describe 'GET #index' do + before(:each) { Tour.all.each { |tour| tour.update(published: false) } } - describe "GET #show" do - it "returns a success response" do - tour_medium = TourMedium.create! valid_attributes - get :show, params: {id: tour_medium.to_param}, session: valid_session - expect(response).to be_success - end - end + context 'unauthenticated' do + it 'returns a success response but zero TourMedium objects' do - describe "POST #create" do - context "with valid params" do - it "creates a new TourMedium" do - expect { - post :create, params: {v3_tour_medium: valid_attributes}, session: valid_session - }.to change(TourMedium, :count).by(1) + tour_medium = create(:tour_medium, tour: create(:tour, published: false)) + get :index, params: { tenant: Apartment::Tenant.current } + expect(response.status).to eq(200) + expect(json.count).to eq(0) + expect(TourMedium.count).to be > 0 end - it "renders a JSON response with the new v3_tour_medium" do + it 'returns a success response but zero TourMedium objects' do + tour_medium = create(:tour_medium, tour: create(:tour, published: true)) + get :index, params: { tenant: Apartment::Tenant.current } + expect(response.status).to eq(200) + expect(json.count).to eq(1) + end + end + + context 'authenticated unauthorized' do + + it 'returns zero TourMedium objects, not current tenant admin, non tour author' do + original_tenant = Apartment::Tenant.current + tour_medium = create(:tour_medium, tour: create(:tour, published: false)) + tour_set = create(:tour_set) + user = create(:user, super: false) + user.tour_sets << tour_set + signed_cookie(user) + get :index, params: { tenant: original_tenant } + Apartment::Tenant.switch! original_tenant + expect(response.status).to eq(200) + expect(json.count).to eq(0) + expect(TourMedium.count).to be > 0 + end - post :create, params: {v3_tour_medium: valid_attributes}, session: valid_session - expect(response).to have_http_status(:created) - expect(response.content_type).to eq('application/json') - expect(response.location).to eq(v3_tour_medium_url(TourMedium.last)) + it 'returns zero TourMedium objects, not current tenant admin, non tour author' do + original_tenant = Apartment::Tenant.current + tour_medium = create(:tour_medium, tour: create(:tour, published: false)) + tour_set = create(:tour_set) + Apartment::Tenant.switch! tour_set.subdir + user = create(:user, super: false) + user.tours << create(:tour) + signed_cookie(user) + Apartment::Tenant.switch! original_tenant + get :index, params: { tenant: original_tenant } + expect(response.status).to eq(200) + expect(json.count).to eq(0) + expect(TourMedium.count).to be > 0 end end - context "with invalid params" do - it "renders a JSON response with errors for the new v3_tour_medium" do + context 'authenticated and authorized' do + it 'returns all TourMedium objects to super' do + create_list(:tour_medium, 4) + user = create(:user, super: true) + signed_cookie(user) + get :index, params: { tenant: Apartment::Tenant.current } + expect(response.status).to eq(200) + expect(json.count).to eq(TourMedium.count) + end - post :create, params: {v3_tour_medium: invalid_attributes}, session: valid_session - expect(response).to have_http_status(:unprocessable_entity) - expect(response.content_type).to eq('application/json') + it 'returns all TourMedium objects to tenant admin' do + create_list(:tour_medium, 4) + user = create(:user, super: false) + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + signed_cookie(user) + get :index, params: { tenant: Apartment::Tenant.current } + expect(response.status).to eq(200) + expect(json.count).to eq(TourMedium.count) end end end - describe "PUT #update" do - context "with valid params" do - let(:new_attributes) { - skip("Add a hash of attributes valid for your model") - } - - it "updates the requested v3_tour_medium" do - tour_medium = TourMedium.create! valid_attributes - put :update, params: {id: tour_medium.to_param, v3_tour_medium: new_attributes}, session: valid_session - tour_medium.reload - skip("Add assertions for updated state") + describe 'GET #show' do + context 'unauthenticated' do + it 'returns a success response but empty TourMedium objects' do + + tour_medium = create(:tour_medium, tour: create(:tour, published: false)) + get :show, params: { id: tour_medium.id, tenant: Apartment::Tenant.current } + expect(response.status).to eq(200) + expect(relationships[:medium][:data]).to be nil + expect(relationships[:tour][:data]).to be nil + expect(tour_medium.tour).not_to be nil + expect(tour_medium.medium).not_to be nil + expect(TourMedium.count).to be > 0 end - it "renders a JSON response with the v3_tour_medium" do - tour_medium = TourMedium.create! valid_attributes + # it 'returns a success response but empty TourMedium objects' do + # tour_medium = create(:tour_medium, tour: create(:tour, published: true)) + # get :index, params: { id: tour_medium.id, tenant: Apartment::Tenant.current } + # expect(response.status).to eq(200) + # expect(json.count).to eq(1) + # end + end - put :update, params: {id: tour_medium.to_param, v3_tour_medium: valid_attributes}, session: valid_session - expect(response).to have_http_status(:ok) - expect(response.content_type).to eq('application/json') + context 'authenticated unauthorized' do + + it 'returns empty TourMedium objects, not current tenant admin, non tour author' do + original_tenant = Apartment::Tenant.current + tour_medium = create(:tour_medium, tour: create(:tour, published: false)) + tour_set = create(:tour_set) + user = create(:user, super: false) + user.tour_sets << tour_set + signed_cookie(user) + Apartment::Tenant.switch! original_tenant + get :show, params: { id: tour_medium.id, tenant: original_tenant } + expect(response.status).to eq(200) + expect(relationships[:medium][:data]).to be nil + expect(relationships[:tour][:data]).to be nil + expect(tour_medium.tour).not_to be nil + expect(tour_medium.medium).not_to be nil + expect(TourMedium.count).to be > 0 end - end - context "with invalid params" do - it "renders a JSON response with errors for the v3_tour_medium" do - tour_medium = TourMedium.create! valid_attributes + it 'returns empty TourMedium objects, not current tenant admin, non tour author' do + original_tenant = Apartment::Tenant.current + tour_medium = create(:tour_medium, tour: create(:tour, published: false)) + tour_set = create(:tour_set) + Apartment::Tenant.switch! tour_set.subdir + user = create(:user, super: false) + user.tours << create(:tour) + signed_cookie(user) + Apartment::Tenant.switch! original_tenant + get :show, params: { id: tour_medium.id, tenant: original_tenant } + expect(response.status).to eq(200) + expect(relationships[:medium][:data]).to be nil + expect(relationships[:tour][:data]).to be nil + expect(TourMedium.count).to be > 0 + end + end - put :update, params: {id: tour_medium.to_param, v3_tour_medium: invalid_attributes}, session: valid_session - expect(response).to have_http_status(:unprocessable_entity) - expect(response.content_type).to eq('application/json') + context 'authenticated and authorized' do + it 'returns all TourMedium objects to super' do + create_list(:tour_medium, 4) + user = create(:user, super: true) + signed_cookie(user) + get :show, params: { id: TourMedium.last.id, tenant: Apartment::Tenant.current } + expect(response.status).to eq(200) + expect(json.count).to eq(TourMedium.count) end end end - describe "DELETE #destroy" do - it "destroys the requested v3_tour_medium" do - tour_medium = TourMedium.create! valid_attributes + describe 'POST #create' do + it 'returns does not create a new TourMedium' do expect { - delete :destroy, params: {id: tour_medium.to_param}, session: valid_session - }.to change(TourMedium, :count).by(-1) + post :create, params: { tenant: Apartment::Tenant.current } + }.to change(TourMedium, :count).by(0) end + + it 'returns 401' do + user = create(:user, super: true) + signed_cookie(user) + post :create, params: { data: { type: 'tour_media', attributes: { tour_id: 1, medium_id: 1, position: 1 } }, tenant: Apartment::Tenant.current } + expect(response.status).to eq(401) + end + end + + describe 'PUT #update' do + context 'with valid params' do + # let(:new_attributes) { + # skip('Add a hash of attributes valid for your model') + # } + + # it 'updates the requested v3_tour_medium' do + # tour_medium = TourMedium.create! valid_attributes + # put :update, params: {id: tour_medium.to_param, v3_tour_medium: new_attributes}, session: valid_session + # tour_medium.reload + # skip('Add assertions for updated state') + # end + + it 'renders a JSON response with the v3_tour_medium' do + tour_medium = create(:tour_medium, position: 1) + expect(tour_medium.position).not_to eq(100) + user = create(:user, super: true) + signed_cookie(user) + put :update, params: { id: tour_medium.id, data: { type: 'tour_media', attributes: { tour_id: tour_medium.tour.id, medium_id: tour_medium.medium.id, position: 100 } }, tenant: Apartment::Tenant.current } + expect(response).to have_http_status(:ok) + expect(attributes[:position]).to eq(100) + expect(TourMedium.find(tour_medium.id).position).to eq(100) + end + end + + # context 'with invalid params' do + # it 'renders a JSON response with errors for the v3_tour_medium' do + # tour_medium = TourMedium.create! valid_attributes + + # put :update, params: {id: tour_medium.to_param, v3_tour_medium: invalid_attributes}, session: valid_session + # expect(response).to have_http_status(:unprocessable_entity) + # expect(response.content_type).to eq('application/json') + # end + # end end + describe 'DELETE #destroy' do + it 'does not destroy the requested v3_tour_medium' do + tour_medium = create(:tour_medium) + user = create(:user, super: true) + signed_cookie(user) + expect { + delete :destroy, params: { id: tour_medium.to_param, tenant: Apartment::Tenant.current } + }.to change(TourMedium, :count).by(0) + end + + it 'responds with 405' do + tour_medium = create(:tour_medium) + user = create(:user, super: true) + signed_cookie(user) + delete :destroy, params: { id: tour_medium.to_param, tenant: Apartment::Tenant.current } + expect(response.status).to eq(405) + end + end end diff --git a/spec/controllers/v3/tour_modes_controller_spec.rb b/spec/controllers/v3/tour_modes_controller_spec.rb deleted file mode 100644 index aa2e7eac..00000000 --- a/spec/controllers/v3/tour_modes_controller_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe V3::TourModesController, type: :controller do - -end diff --git a/spec/controllers/v3/tour_set_admins_controller_spec.rb b/spec/controllers/v3/tour_set_admins_controller_spec.rb new file mode 100644 index 00000000..196290ee --- /dev/null +++ b/spec/controllers/v3/tour_set_admins_controller_spec.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe V3::TourSetAdminsController, type: :controller do + before(:each) { + create_list(:tour_set, rand(2..5)) + TourSet.all.each { |tour_set| tour_set.update(admins: create_list(:user, rand(2..5))) } + } + + describe 'GET #index' do + context 'unauthenticated and unauthorized' do + it 'returns 401 when not unauthenticated' do + get :index, params: { tenant: TourSet.first.subdir } + expect(response.status).to eq(401) + end + + it 'returns 401 when not unauthenticated but unauthorized' do + initial_tour_set = TourSet.first + user = create(:user, super: false) + user.tour_sets << create(:tour_set) + expect(user.tour_sets).not_to include initial_tour_set + signed_cookie(user) + Apartment::Tenant.switch! initial_tour_set.subdir + get :index, params: { tenant: initial_tour_set.subdir } + expect(response.status).to eq(401) + end + end + + context 'authorized' do + it 'responds with 200 and a list of TourSetAdmins when requested by tenant admin' do + user = create(:user, super: false) + user.tour_sets << TourSet.last + signed_cookie(user) + Apartment::Tenant.switch! TourSet.last.subdir + get :index, params: { tenant: TourSet.last.subdir } + expect(response.status).to eq(200) + expect(json.first[:type]).to eq('tour_set_admins') + expect(json.count).to eq(TourSetAdmin.count) + end + + it 'responds with 200 and a list of TourSetAdmins when requested by super' do + user = create(:user, super: true) + signed_cookie(user) + Apartment::Tenant.switch! TourSet.first.subdir + get :index, params: { tenant: TourSet.first.subdir } + expect(response.status).to eq(200) + expect(json.first[:type]).to eq('tour_set_admins') + expect(json.count).to eq(TourSetAdmin.count) + end + end + end + + describe 'GET #show' do + it 'returns 405' do + get :show, params: { tenant: Apartment::Tenant.current, id: TourSetAdmin.first.id } + expect(response.status).to eq(405) + end + end + + describe 'POST #create' do + it 'returns 405' do + post :create, params: { tenant: Apartment::Tenant.current, id: TourSetAdmin.first.id } + expect(response.status).to eq(405) + end + end + + describe 'PUT #update' do + it 'returns 405' do + put :update, params: { tenant: Apartment::Tenant.current, id: TourSetAdmin.first.id } + expect(response.status).to eq(405) + end + end + + describe 'DELETE #destroy' do + it 'returns 405' do + delete :destroy, params: { tenant: Apartment::Tenant.current, id: TourSetAdmin.first.id } + expect(response.status).to eq(405) + end + end +end diff --git a/spec/controllers/v3/tour_set_users_controller_spec.rb b/spec/controllers/v3/tour_set_users_controller_spec.rb deleted file mode 100644 index 204ad3f3..00000000 --- a/spec/controllers/v3/tour_set_users_controller_spec.rb +++ /dev/null @@ -1,129 +0,0 @@ -require 'rails_helper' - -# This spec was generated by rspec-rails when you ran the scaffold generator. -# It demonstrates how one might use RSpec to specify the controller code that -# was generated by Rails when you ran the scaffold generator. -# -# It assumes that the implementation code is generated by the rails scaffold -# generator. If you are using any extension libraries to generate different -# controller code, this generated spec may or may not pass. -# -# It only uses APIs available in rails and/or rspec-rails. There are a number -# of tools you can use to make these specs even more expressive, but we're -# sticking to rails and rspec-rails APIs to keep things simple and stable. -# -# Compared to earlier versions of this generator, there is very limited use of -# stubs and message expectations in this spec. Stubs are only used when there -# is no simpler way to get a handle on the object needed for the example. -# Message expectations are only used when there is no simpler way to specify -# that an instance is receiving a specific message. -# -# Also compared to earlier versions of this generator, there are no longer any -# expectations of assigns and templates rendered. These features have been -# removed from Rails core in Rails 5, but can be added back in via the -# `rails-controller-testing` gem. - -RSpec.describe V3::TourSetAdminsController, type: :controller do - - # This should return the minimal set of attributes required to create a valid - # TourSetAdmin. As you add validations to TourSetAdmin, be sure to - # adjust the attributes here as well. - let(:valid_attributes) { - skip("Add a hash of attributes valid for your model") - } - - let(:invalid_attributes) { - skip("Add a hash of attributes invalid for your model") - } - - # This should return the minimal set of values that should be in the session - # in order to pass any filters (e.g. authentication) defined in - # TourSetAdminsController. Be sure to keep this updated too. - let(:valid_session) { {} } - - describe "GET #index" do - it "returns a success response" do - tour_set_admin = TourSetAdmin.create! valid_attributes - get :index, params: {}, session: valid_session - expect(response).to be_success - end - end - - describe "GET #show" do - it "returns a success response" do - tour_set_admin = TourSetAdmin.create! valid_attributes - get :show, params: {id: tour_set_admin.to_param}, session: valid_session - expect(response).to be_success - end - end - - describe "POST #create" do - context "with valid params" do - it "creates a new TourSetAdmin" do - expect { - post :create, params: {tour_set_admin: valid_attributes}, session: valid_session - }.to change(TourSetAdmin, :count).by(1) - end - - it "renders a JSON response with the new tour_set_admin" do - - post :create, params: {tour_set_admin: valid_attributes}, session: valid_session - expect(response).to have_http_status(:created) - expect(response.content_type).to eq('application/json') - expect(response.location).to eq(tour_set_admin_url(TourSetAdmin.last)) - end - end - - context "with invalid params" do - it "renders a JSON response with errors for the new tour_set_admin" do - - post :create, params: {tour_set_admin: invalid_attributes}, session: valid_session - expect(response).to have_http_status(:unprocessable_entity) - expect(response.content_type).to eq('application/json') - end - end - end - - describe "PUT #update" do - context "with valid params" do - let(:new_attributes) { - skip("Add a hash of attributes valid for your model") - } - - it "updates the requested tour_set_admin" do - tour_set_admin = TourSetAdmin.create! valid_attributes - put :update, params: {id: tour_set_admin.to_param, tour_set_admin: new_attributes}, session: valid_session - tour_set_admin.reload - skip("Add assertions for updated state") - end - - it "renders a JSON response with the tour_set_admin" do - tour_set_admin = TourSetAdmin.create! valid_attributes - - put :update, params: {id: tour_set_admin.to_param, tour_set_admin: valid_attributes}, session: valid_session - expect(response).to have_http_status(:ok) - expect(response.content_type).to eq('application/json') - end - end - - context "with invalid params" do - it "renders a JSON response with errors for the tour_set_admin" do - tour_set_admin = TourSetAdmin.create! valid_attributes - - put :update, params: {id: tour_set_admin.to_param, tour_set_admin: invalid_attributes}, session: valid_session - expect(response).to have_http_status(:unprocessable_entity) - expect(response.content_type).to eq('application/json') - end - end - end - - describe "DELETE #destroy" do - it "destroys the requested tour_set_admin" do - tour_set_admin = TourSetAdmin.create! valid_attributes - expect { - delete :destroy, params: {id: tour_set_admin.to_param}, session: valid_session - }.to change(TourSetAdmin, :count).by(-1) - end - end - -end diff --git a/spec/controllers/v3/tour_sets_controller_spec.rb b/spec/controllers/v3/tour_sets_controller_spec.rb index 5727837a..5c383f5a 100644 --- a/spec/controllers/v3/tour_sets_controller_spec.rb +++ b/spec/controllers/v3/tour_sets_controller_spec.rb @@ -2,40 +2,15 @@ require 'rails_helper' -# This spec was generated by rspec-rails when you ran the scaffold generator. -# It demonstrates how one might use RSpec to specify the controller code that -# was generated by Rails when you ran the scaffold generator. -# -# It assumes that the implementation code is generated by the rails scaffold -# generator. If you are using any extension libraries to generate different -# controller code, this generated spec may or may not pass. -# -# It only uses APIs available in rails and/or rspec-rails. There are a number -# of tools you can use to make these specs even more expressive, but we're -# sticking to rails and rspec-rails APIs to keep things simple and stable. -# -# Compared to earlier versions of this generator, there is very limited use of -# stubs and message expectations in this spec. Stubs are only used when there -# is no simpler way to get a handle on the object needed for the example. -# Message expectations are only used when there is no simpler way to specify -# that an instance is receiving a specific message. -# -# Also compared to earlier versions of this generator, there are no longer any -# expectations of assigns and templates rendered. These features have been -# removed from Rails core in Rails 5, but can be added back in via the -# `rails-controller-testing` gem. - RSpec.describe V3::TourSetsController, type: :controller do # This should return the minimal set of attributes required to create a valid # TourSet. As you add validations to TourSet, be sure to # adjust the attributes here as well. let(:valid_attributes) { - skip('Add a hash of attributes valid for your model') } let(:invalid_attributes) { - skip('Add a hash of attributes invalid for your model') } # This should return the minimal set of values that should be in the session @@ -43,89 +18,354 @@ # TourSetsController. Be sure to keep this updated too. let(:valid_session) { {} } - describe 'GET #index' do - it 'returns a success response' do - tour_set = TourSet.create! valid_attributes - get :index, params: {}, session: valid_session - expect(response).to be_success - end - end + describe 'TourSetsController' do + let(:valid_params) { { data: { type: 'tour_sets', attributes: { name: Faker::Music::Hiphop.artist } }, tenant: 'public' } } + let(:invalid_params) { { data: { type: 'tour_sets', attributes: { name: nil } }, tenant: 'public' } } - describe 'GET #show' do - it 'returns a success response' do - tour_set = TourSet.create! valid_attributes - get :show, params: { id: tour_set.to_param }, session: valid_session - expect(response).to be_success + before(:each) do + Apartment::Tenant.reset + TourSet.all.each { |ts| ts.delete } + create_list(:tour_set, rand(3..5)) end - end - describe 'POST #create' do - context 'with valid params' do - it 'creates a new TourSet' do - expect { - post :create, params: { tour_set: valid_attributes }, session: valid_session - }.to change(TourSet, :count).by(1) + describe 'GET #index' do + it 'returns a success response but return no TourSet objects' do + get :index, params: { tenant: 'public' } + expect(response.status).to eq(200) + expect(json.count).to eq(0) end - it 'renders a JSON response with the new tour_set' do + it 'returns a success response and returns TourSet objects with tours that are published and have stops' do + Apartment::Tenant.switch! TourSet.second.subdir + tour = create(:tour, published: true) + tour.stops << create(:stop) + get :index, params: { tenant: 'public' } + expect(response.status).to eq(200) + expect(json.count).to eq(1) + end - post :create, params: { tour_set: valid_attributes }, session: valid_session - expect(response).to have_http_status(:created) - expect(response.content_type).to eq('application/json') - expect(response.location).to eq(tour_set_url(TourSet.last)) + it 'returns a success response by subdir but returns no TourSet objects when no published tours and not authorized' do + get :index, params: { tenant: 'public', subdir: TourSet.last.subdir } + expect(response.status).to eq(200) + expect(json.count).to eq(0) + end + + it 'returns a success response and only TourSet object by subdir when tour set has a published tour and not authorized' do + TourSet.all.each do |ts| + Apartment::Tenant.switch! ts.subdir + tour = create(:tour, published: true) + tour.stops << create(:stop) + end + Apartment::Tenant.reset + expect(TourSet.all.reject { |ts| ts.published_tours.empty? }.count).to be > 1 + get :index, params: { tenant: 'public', subdir: TourSet.second.subdir } + expect(response.status).to eq(200) + expect(json.count).to eq(1) + expect(attributes.first[:name]).to eq(TourSet.second.name) + end + + it 'returns a success response and only one TourSet object by subdir when tour set has a published tour and not authorized' do + Apartment::Tenant.switch! TourSet.second.subdir + tour = create(:tour, published: true) + tour.stops << create(:stop) + get :index, params: { tenant: 'public', subdir: TourSet.second.subdir } + expect(response.status).to eq(200) + expect(json.count).to eq(1) + end + + it 'returns all TourSet objects when requested by super' do + user = create(:user, super: true) + signed_cookie(user) + get :index, params: { tenant: 'public' } + expect(response.status).to eq(200) + expect(json.count).to eq(TourSet.count) + end + + it 'returns TourSet objects when requested by admin' do + user = create(:user, super: false) + user.tour_sets << [TourSet.first, TourSet.last] + + # Make sure a set doesn't slip in because of published tours. + [TourSet.first.subdir, TourSet.last.subdir].each do |ts| + Apartment::Tenant.switch! ts + Tour.all.update(published: false) + end + + # Make a new set with published tour to make sure it's included. + published_set = create(:tour_set) + Apartment::Tenant.switch! published_set.subdir + create(:tour, published: true, stops: create_list(:stop, 2)) + + Apartment::Tenant.reset + signed_cookie(user) + get :index, params: { tenant: 'public' } + expect(response.status).to eq(200) + expect(json.count).to be > 2 + end + + it 'returns no TourSet objects when requested by non admin' do + user = create(:user, super: false) + user.tour_sets = [] + signed_cookie(user) + get :index, params: { tenant: 'public' } + expect(response.status).to eq(200) + expect(json.count).to eq(0) + end + + it 'returns one unpublished TourSet object when requested by subdir and by super' do + user = create(:user, super: true) + signed_cookie(user) + Apartment::Tenant.switch! TourSet.second.subdir + Tour.all.each { |t| t.update(published: false) } + Apartment::Tenant.reset + get :index, params: { tenant: 'public', subdir: TourSet.second.subdir } + expect(response.status).to eq(200) + expect(json.count).to eq(1) + expect(attributes.first[:name]).to eq(TourSet.second.name) + end + + it 'returns one unpublished TourSet object when requested by subdir and by super' do + user = create(:user, super: false) + user.tour_sets << TourSet.last + signed_cookie(user) + Apartment::Tenant.switch! TourSet.last.subdir + Tour.all.each { |t| t.update(published: false) } + Apartment::Tenant.reset + get :index, params: { tenant: 'public', subdir: TourSet.last.subdir } + expect(response.status).to eq(200) + expect(json.count).to eq(1) + expect(attributes.first[:name]).to eq(TourSet.last.name) end end - context 'with invalid params' do - it 'renders a JSON response with errors for the new tour_set' do + describe 'GET #show' do + context 'unauthenticated' do + it 'returns a success response and dummy TourSet when no tour is published' do + get :show, params: { tenant: 'public', id: TourSet.first.to_param } + expect(response.status).to eq(200) + expect(attributes[:name]).to eq('....') + end + + it 'returns a success response and TourSet when TourSet has published tour' do + Apartment::Tenant.switch! TourSet.last.subdir + tour = create(:tour, published: true) + tour.stops << create(:stop) + get :show, params: { tenant: 'public', id: TourSet.last.to_param } + expect(response.status).to eq(200) + expect(attributes[:name]).to eq(TourSet.last.name) + expect(relationships[:admins][:data]).to be_empty + end + end + + context 'authenticated unauthorized' do + it 'returns a success response and dummy TourSet when no tour is published' do + user = create(:user, super: false) + user.tour_sets = [] + signed_cookie(user) + get :show, params: { tenant: 'public', id: TourSet.first.to_param } + expect(response.status).to eq(200) + expect(attributes[:name]).to eq('....') + end + + it 'returns a success response and TourSet when TourSet has published tour' do + user = create(:user, super: false) + user.tour_sets << TourSet.first + signed_cookie(user) + get :show, params: { tenant: 'public', id: TourSet.last.to_param } + expect(response.status).to eq(200) + expect(attributes[:name]).to eq('....') + end + end + + context 'authenticated authorized' do + it 'returns a success response and TourSet when requested by tenant adamin' do + user = create(:user, super: false) + user.tour_sets << TourSet.last + signed_cookie(user) + get :show, params: { tenant: 'public', id: TourSet.last.to_param } + expect(response.status).to eq(200) + expect(attributes[:name]).to eq(TourSet.last.name) + expect(relationships[:admins][:data].map { |admin| admin[:id] }).to include(user.id.to_s) + end - post :create, params: { tour_set: invalid_attributes }, session: valid_session - expect(response).to have_http_status(:unprocessable_entity) - expect(response.content_type).to eq('application/json') + it 'returns a success response and TourSet when requested by super' do + user = create(:user, super: true) + signed_cookie(user) + get :show, params: { tenant: 'public', id: TourSet.last.to_param } + expect(response.status).to eq(200) + expect(attributes[:name]).to eq(TourSet.last.name) + end end end - end - describe 'PUT #update' do - context 'with valid params' do - let(:new_attributes) { - skip('Add a hash of attributes valid for your model') - } + describe 'POST #create' do + context 'when unauthenticated and unauthorized' do + it 'does not create a new TourSet when not super' do + expect { + post :create, params: valid_params + }.to change(TourSet, :count).by(0) + end - it 'updates the requested tour_set' do - tour_set = TourSet.create! valid_attributes - put :update, params: { id: tour_set.to_param, tour_set: new_attributes }, session: valid_session - tour_set.reload - skip('Add assertions for updated state') + it 'returns 401' do + post :create, params: valid_params + expect(response).to have_http_status(401) + end end - it 'renders a JSON response with the tour_set' do - tour_set = TourSet.create! valid_attributes + context 'when authenticated but unauthorized' do + it 'does not create a new TourSet when not super' do + user = create(:user, super: false) + signed_cookie(user) + expect { + post :create, params: valid_params + }.to change(TourSet, :count).by(0) + end + + it 'returns 401 when not super' do + user = create(:user, super: false) + signed_cookie(user) + post :create, params: valid_params + expect(response).to have_http_status(401) + end + + it 'does not create a new TourSet when not super but is a tenant admin' do + user = create(:user, super: false) + user.tour_sets << TourSet.second + signed_cookie(user) + expect { + post :create, params: valid_params + }.to change(TourSet, :count).by(0) + end - put :update, params: { id: tour_set.to_param, tour_set: valid_attributes }, session: valid_session - expect(response).to have_http_status(:ok) - expect(response.content_type).to eq('application/json') + it 'returns 401 when not super but is a tenant admin' do + user = create(:user, super: false) + user.tour_sets << TourSet.first + signed_cookie(user) + post :create, params: valid_params + expect(response).to have_http_status(401) + end + end + + context 'when authenticated and authorized' do + context 'valid params' do + it 'creates a new TourSet' do + user = create(:user, super: true) + signed_cookie(user) + expect { + post :create, params: valid_params + }.to change(TourSet, :count).by(1) + end + + it 'renders a JSON response with the new tour_set' do + user = create(:user, super: true) + signed_cookie(user) + post :create, params: valid_params + expect(response).to have_http_status(:created) + expect(response.content_type).to eq('application/json; charset=utf-8') + end + end + end + + context 'with invalid params' do + it 'renders a JSON response with errors for the new tour_set' do + user = create(:user, super: true) + signed_cookie(user) + post :create, params: invalid_params + expect(response).to have_http_status(:unprocessable_entity) + end end end - context 'with invalid params' do - it 'renders a JSON response with errors for the tour_set' do - tour_set = TourSet.create! valid_attributes + describe 'PUT #update' do + context 'when unauthenticated and unauthorized' do + it 'returns 401' do + put :update, params: valid_params.merge({ id: TourSet.last.to_param }) + expect(response).to have_http_status(401) + end + end - put :update, params: { id: tour_set.to_param, tour_set: invalid_attributes }, session: valid_session - expect(response).to have_http_status(:unprocessable_entity) - expect(response.content_type).to eq('application/json') + context 'when authenticated but unauthorized' do + it 'does not update a TourSet when not super' do + user = create(:user, super: false) + signed_cookie(user) + put :update, params: valid_params.merge({ id: TourSet.first.to_param }) + expect(response).to have_http_status(401) + end + + it 'allows update TourSet when not super but is a tenant admin' do + user = create(:user, super: false) + user.tour_sets << TourSet.second + signed_cookie(user) + put :update, params: valid_params.merge({ id: TourSet.second.to_param }) + expect(response).to have_http_status(200) + end + + it 'does not update TourSet when not super not a tenant admin but is a tour author' do + Apartment::Tenant.switch! TourSet.second.subdir + tour = create(:tour) + user = create(:user, super: false) + user.tour_sets = [] + user.tours << tour + signed_cookie(user) + put :update, params: valid_params.merge({ id: TourSet.second.to_param }) + expect(response).to have_http_status(401) + end + + end + + context 'when authenticated and authorized' do + context 'valid params' do + it 'renders a JSON response with the new tour_set' do + new_name = Faker::Music::Hiphop.artist + valid_params[:data][:attributes][:name] = new_name + user = create(:user, super: true) + signed_cookie(user) + put :update, params: valid_params.merge({ id: TourSet.last.to_param }) + expect(response).to have_http_status(200) + expect(attributes[:name]).to eq(new_name) + end + end + + context 'valid params' do + it 'renders a JSON response with the new tour_set and purges icon' do + tour_set = create(:tour_set) + tour_set.update( + logo_title: Faker::File.file_name(dir: '', ext: 'png', directory_separator: ''), + base_sixty_four: File.read(Rails.root.join('spec/factories/base64_image.txt')) + ) + expect(TourSet.find(tour_set.id).logo.attached?).to be true + serialized_tour_set = JSON.parse(ActiveModelSerializers::Adapter::JsonApi.new(V3::TourSetSerializer.new(tour_set)).to_json).with_indifferent_access + serialized_tour_set[:data][:attributes][:base_sixty_four] = nil + serialized_tour_set[:data][:attributes][:logo] = nil + user = create(:user, super: true) + signed_cookie(user) + put :update, params: { data: serialized_tour_set[:data], id: tour_set.id, tenant: 'public' } + expect(response).to have_http_status(200) + expect(TourSet.find(tour_set.id).logo.attached?).to be false + end + end + end + + context 'with invalid params' do + it 'renders a JSON response with errors for the new tour_set' do + user = create(:user, super: true) + signed_cookie(user) + put :update, params: invalid_params.merge({ id: TourSet.first.to_param }) + expect(response).to have_http_status(:unprocessable_entity) + end end end - end - describe 'DELETE #destroy' do - it 'destroys the requested tour_set' do - tour_set = TourSet.create! valid_attributes - expect { - delete :destroy, params: { id: tour_set.to_param }, session: valid_session - }.to change(TourSet, :count).by(-1) + describe 'DELETE #destroy' do + it 'destroys the requested tour_set' do + tour_set = create(:tour_set) + user = create(:user, super: true) + signed_cookie(user) + Apartment::Tenant.reset + expect { + delete :destroy, params: { id: tour_set.to_param, tenant: Apartment::Tenant.current } + }.to change(TourSet, :count).by(-1) + end end end - end diff --git a/spec/controllers/v3/tour_stops_controller_spec.rb b/spec/controllers/v3/tour_stops_controller_spec.rb index 98b7db9f..4d26035c 100644 --- a/spec/controllers/v3/tour_stops_controller_spec.rb +++ b/spec/controllers/v3/tour_stops_controller_spec.rb @@ -3,5 +3,387 @@ require 'rails_helper' RSpec.describe V3::TourStopsController, type: :controller do + def data(tour, stop, position = 1) + { + type: 'tour_stops', + attributes: { position: position }, + relationships: { + tour: { data: { type: 'tours', id: tour.id } }, + stop: { data: { type: 'stops', id: stop.id } } + } + } + end + describe 'GET #index' do + it 'returns a 200 response and empty tour when none are part of a published tour' do + Tour.all.each { |tour| tour.update(published: false) } + get :index, params: { tenant: Apartment::Tenant.current } + expect(json).to be_empty + expect(response.status).to eq(200) + end + + it 'returns a 200 response and only tour stops that are part of a published tour' do + create_list(:tour_with_stops, 5, theme: create(:theme), mode: create(:mode)) + Tour.first.update(published: true) if Tour.published.empty? + Tour.last.update(published: false) if Tour.published.count == Tour.count + get :index, params: { tenant: Apartment::Tenant.current } + expect(response.status).to eq(200) + expect(json.count).to eq(Tour.published.map { |tour| tour.tour_stops.count }.sum) + end + + it 'returns a 200 response when requeted by slug' do + tour = create(:tour_with_stops) + tour.update(published: true) + get :index, params: { tenant: Apartment::Tenant.current, slug: tour.tour_stops.first.stop.slug, tour: tour.id } + expect(response.status).to eq(200) + expect(included.first[:attributes][:title]).to eq(tour.tour_stops.first.stop.title) + end + + it 'returns a 200 response when request is authenticated by tenant admin and tour is unpublished' do + tour = create(:tour_with_stops, published: false) + tour.update(published: false) + user = create(:user) + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + signed_cookie(user) + get :index, params: { tenant: Apartment::Tenant.current, slug: tour.tour_stops.first.stop.slug, tour: tour.id } + expect(response.status).to eq(200) + expect(included.first[:attributes][:title]).to eq(tour.tour_stops.first.stop.title) + end + + it 'returns a 200 response when request is authenticated by tour author and tour is unpublished' do + tour = create(:tour_with_stops, published: false) + tour.update(published: false) + user = create(:user) + user.tour_sets = [] + user.tours << tour + signed_cookie(user) + get :index, params: { tenant: Apartment::Tenant.current, slug: tour.tour_stops.first.stop.slug, tour: tour.id } + expect(response.status).to eq(200) + expect(included.first[:attributes][:title]).to eq(tour.tour_stops.first.stop.title) + end + + it 'returns empty TourStop when `fastboo` in params' do + create(:tour_with_stops, published: true) + get :index, params: { tenant: Apartment::Tenant.current, fastboot: 'true' } + expect(response.status).to eq(200) + expect(json[:id]).to eq(0) + end + + it 'returns empty json when unauthenticated but tour is not published' do + tour = create(:tour_with_stops, published: false) + get :index, params: { tenant: Apartment::Tenant.current, slug: tour.tour_stops.first.stop.slug, tour: tour.id } + expect(json).to be nil + end + + it 'returns all TourStops when requested by tenant admin' do + create_list(:tour_with_stops, rand(4..7)) + user = create(:user, super: false) + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + signed_cookie(user) + get :index, params: { tenant: Apartment::Tenant.current } + expect(json.count).to eq(TourStop.count) + end + end + + describe 'GET #show' do + it 'returns a 200 response' do + tours = create(:tour_with_stops) + tour = Tour.last + tour.update(published: true) + get :show, params: { tenant: Apartment::Tenant.current, id: tour.tour_stops.last.id } + expect(response.status).to eq(200) + expect(relationships[:tour][:data][:id]).to eq(tour.id.to_s) + end + + it 'returns a 200 response when request is authenticated by tour author and tour is unpublished' do + tour = create(:tour_with_stops) + tour.update(published: false) + user = create(:user) + user.tour_sets = [] + user.tours << tour + signed_cookie(user) + get :show, params: { tenant: Apartment::Tenant.current, id: tour.tour_stops.last.id } + expect(response.status).to eq(200) + expect(relationships[:tour][:data][:id]).to eq(tour.id.to_s) + end + + it 'returns a 200 response when request is authenticated by tenant admin and tour is unpublished' do + tour = create(:tour_with_stops) + tour.update(published: false) + user = create(:user) + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + signed_cookie(user) + get :show, params: { tenant: Apartment::Tenant.current, id: tour.tour_stops.last.id } + expect(response.status).to eq(200) + expect(relationships[:tour][:data][:id]).to eq(tour.id.to_s) + end + + it 'returns a 200 response and empty json when tour is unpublished and request is not authenticated' do + tour = create(:tour_with_stops) + tour.update(published: false) + get :show, params: { tenant: Apartment::Tenant.current, id: tour.tour_stops.last.id } + expect(response.status).to eq(200) + expect(json).to be_empty + end + + it 'returns a 200 response and empty json when tour is unpublished and request is authenticated by someone who is nither a tenant admin or tour author' do + tour = create(:tour_with_stops) + tour.update(published: false) + user = create(:user) + user.tours = [] + user.tour_sets = [] + signed_cookie(user) + get :show, params: { tenant: Apartment::Tenant.current, id: tour.tour_stops.last.id } + expect(response.status).to eq(200) + expect(json).to be_empty + end + end + + # TourStop objects are NOT created via tha API. Every test should return 401 + describe 'POST #create' do + context 'with valid params' do + it 'return 405 when unauthenciated' do + tour = create(:tour) + stop = create(:stop) + post :create, params: { data: data(tour, stop), tenant: TourSet.first.subdir } + expect(response.status).to eq(405) + end + + it 'return 405 when authenciated but not an admin for current tenant' do + tour = create(:tour) + stop = create(:stop) + original_tour_stop_count = TourStop.count + user = create(:user) + user.update(super: false) + user.tour_sets = [] + user.tours = [] + signed_cookie(user) + post :create, params: { data: data(tour, stop), tenant: Apartment::Tenant.current } + expect(response.status).to eq(405) + expect(original_tour_stop_count).to eq(TourStop.count) + end + + it 'return 405 when authenciated but an admin for current tenant' do + tour = create(:tour) + stop = create(:stop) + original_tour_stop_count = TourStop.count + user = create(:user) + user.update(super: false) + user.tours = [] + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + signed_cookie(user) + post :create, params: { data: data(tour, stop), tenant: Apartment::Tenant.current } + expect(response.status).to eq(405) + expect(original_tour_stop_count).to eq(TourStop.count) + end + + it 'return 405 when authenciated by super' do + tour = create(:tour) + stop = create(:stop) + original_tour_stop_count = TourStop.count + user = create(:user) + user.tours = [] + user.tour_sets = [] + user.update(super: true) + signed_cookie(user) + post :create, params: { data: data(tour, stop), tenant: Apartment::Tenant.current } + expect(response.status).to eq(405) + expect(original_tour_stop_count).to eq(TourStop.count) + end + + it 'return 405 when authenciated by tour author' do + tour = create(:tour) + stop = create(:stop) + original_tour_stop_count = TourStop.count + user = create(:user) + user.tours << tour + user.tour_sets = [] + user.update(super: false) + signed_cookie(user) + post :create, params: { data: data(tour, stop), tenant: Apartment::Tenant.current } + expect(response.status).to eq(405) + expect(original_tour_stop_count).to eq(TourStop.count) + end + end + end + + describe 'PUT #update' do + context 'with valid params' do + it 'return 401 when unauthenciated' do + tour = create(:tour) + stop = create(:stop) + tour.stops << stop + request_data = data(tour, stop, 4) + request_data[:id] = TourStop.find_by(tour: tour, stop: stop).id + post :update, params: { id: request_data[:id], data: request_data, tenant: TourSet.first.subdir } + expect(response.status).to eq(401) + end + + it 'return 401 when authenciated but not an admin for current tenant' do + tour = create(:tour) + stop = create(:stop) + tour.stops << stop + user = create(:user) + user.update(super: false) + user.tour_sets = [] + user.tours = [] + signed_cookie(user) + request_data = data(tour, stop, 5) + request_data[:id] = TourStop.find_by(tour: tour, stop: stop).id + post :update, params: { id: request_data[:id], data: request_data, tenant: TourSet.first.subdir } + expect(response.status).to eq(401) + end + + it 'return 200 and updated tour when authenciated but an admin for current tenant' do + tour = create(:tour) + stops = create_list(:stop, 5) + stops.each { |stop| tour.stops << stop } + tour.save + stop = Stop.find(stops.first.id) + tour.stops << stop + user = create(:user) + user.update(super: false) + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + user.tours = [] + signed_cookie(user) + tour_stop = TourStop.find_by(tour: tour, stop: stop) + tour_stop.update(position: 2) + expect(TourStop.find(tour_stop.id).position).to eq(2) + request_data = data(tour, stop, 5) + request_data[:id] = tour_stop.id + post :update, params: { id: tour_stop.id, data: request_data, tenant: TourSet.first.subdir } + expect(response.status).to eq(200) + expect(attributes[:position]).not_to eq('5') + expect(TourStop.find(tour_stop.id).position).to eq(5) + end + + it 'return 200 and updated tour when authenciated by super' do + tour = create(:tour) + stops = create_list(:stop, 5) + stops.each { |stop| tour.stops << stop } + tour.save + stop = Stop.find(stops.first.id) + tour.stops << stop + user = create(:user) + user.update(super: true) + user.tour_sets = [] + user.tours = [] + signed_cookie(user) + tour_stop = TourStop.find_by(tour: tour, stop: stop) + tour_stop.update(position: 3) + expect(TourStop.find(tour_stop.id).position).to eq(3) + request_data = data(tour, stop, 4) + request_data[:id] = tour_stop.id + post :update, params: { id: tour_stop.id, data: request_data, tenant: TourSet.first.subdir } + expect(response.status).to eq(200) + expect(attributes[:position]).not_to eq('4') + expect(TourStop.find(tour_stop.id).position).to eq(4) + end + + it 'return 200 and updated tour when authenciated by tour author' do + tour = create(:tour) + stops = create_list(:stop, 5) + stops.each { |stop| tour.stops << stop } + tour.save + stop = Stop.find(stops.first.id) + tour.stops << stop + user = create(:user) + user.update(super: false) + user.tour_sets = [] + user.tours << tour + signed_cookie(user) + tour_stop = TourStop.find_by(tour: tour, stop: stop) + tour_stop.update(position: 6) + expect(TourStop.find(tour_stop.id).position).to eq(6) + request_data = data(tour, stop, 1) + request_data[:id] = tour_stop.id + post :update, params: { id: tour_stop.id, data: request_data, tenant: TourSet.first.subdir } + expect(response.status).to eq(200) + expect(attributes[:position]).not_to eq('1') + expect(TourStop.find(tour_stop.id).position).to eq(1) + end + + it 'return 422 authenciated by super but tour stop and position are nil' do + tour = create(:tour, stops: create_list(:stop, rand(4..6))) + user = create(:user, super: true) + tour_stop = TourStop.find_by(tour: tour, stop: tour.stops.first) + request_data = data(tour, tour.stops.first, 4) + request_data[:relationships][:tour][:data] = nil + request_data[:relationships][:stop][:data] = nil + request_data[:attributes][:position] = nil + signed_cookie(user) + post :update, params: { id: tour_stop.id, data: request_data, tenant: TourSet.first.subdir } + expect(response.status).to eq(422) + expect(errors).to include('Tour must exist') + expect(errors).to include('Stop must exist') + expect(errors).to include('Position can\'t be blank') + end + end + end + + describe 'DELETE #destroy' do + it 'return 405 when unauthenciated' do + tour = create(:tour) + stop = create(:stop) + tour.stops << stop + tour_stop = TourStop.find_by(tour: tour, stop: stop) + post :destroy, params: { id: tour_stop.id, tenant: TourSet.first.subdir } + expect(response.status).to eq(405) + end + + it 'return 405 when authenciated but not an admin for current tenant' do + tour = create(:tour) + stop = create(:stop) + tour.stops << stop + tour_stop = TourStop.find_by(tour: tour, stop: stop) + user = create(:user, super: false) + user.tour_sets = [] + signed_cookie(user) + post :destroy, params: { id: tour_stop.id, tenant: TourSet.first.subdir } + expect(response.status).to eq(405) + end + + it 'return 405 and one less tour when authenciated but an admin for current tenant' do + tour = create(:tour) + stop = create(:stop) + tour.stops << stop + tour_stop = TourStop.find_by(tour: tour, stop: stop) + user = create(:user, super: false) + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + signed_cookie(user) + tour_count = Tour.count + post :destroy, params: { id: tour_stop.id, tenant: Apartment::Tenant.current } + expect(response.status).to eq(405) + expect(Tour.count).to eq(tour_count) + end + + it 'return 405 and one less tour when authenciated by super' do + tour = create(:tour) + stop = create(:stop) + tour.stops << stop + tour_stop = TourStop.find_by(tour: tour, stop: stop) + user = create(:user, super: true) + signed_cookie(user) + tour_count = Tour.count + post :destroy, params: { id: tour_stop.id, tenant: Apartment::Tenant.current } + expect(response.status).to eq(405) + expect(Tour.count).to eq(tour_count) + end + + it 'return 405 and one less tour when authenciated by tour author' do + tour = create(:tour) + stop = create(:stop) + tour.stops << stop + tour_stop = TourStop.find_by(tour: tour, stop: stop) + user = create(:user, super: false) + user.tour_sets = [] + user.tours << tour + signed_cookie(user) + new_title = Faker::Name.unique.name + tour_count = Tour.count + post :destroy, params: { id: tour_stop.id, tenant: Apartment::Tenant.current } + expect(response.status).to eq(405) + expect(Tour.count).to eq(tour_count) + end + end end diff --git a/spec/controllers/v3/tours_controller_spec.rb b/spec/controllers/v3/tours_controller_spec.rb index f03f4ecc..635f7cca 100644 --- a/spec/controllers/v3/tours_controller_spec.rb +++ b/spec/controllers/v3/tours_controller_spec.rb @@ -2,126 +2,397 @@ require 'rails_helper' -# This spec was generated by rspec-rails when you ran the scaffold generator. -# It demonstrates how one might use RSpec to specify the controller code that -# was generated by Rails when you ran the scaffold generator. -# -# It assumes that the implementation code is generated by the rails scaffold -# generator. If you are using any extension libraries to generate different -# controller code, this generated spec may or may not pass. -# -# It only uses APIs available in rails and/or rspec-rails. There are a number -# of tools you can use to make these specs even more expressive, but we're -# sticking to rails and rspec-rails APIs to keep things simple and stable. -# -# Compared to earlier versions of this generator, there is very limited use of -# stubs and message expectations in this spec. Stubs are only used when there -# is no simpler way to get a handle on the object needed for the example. -# Message expectations are only used when there is no simpler way to specify -# that an instance is receiving a specific message. -# -# Also compared to earlier versions of this generator, there are no longer any -# expectations of assigns and templates rendered. These features have been -# removed from Rails core in Rails 5, but can be added back in via the -# `rails-controller-testing` gem. - RSpec.describe V3::ToursController, type: :controller do - # This should return the minimal set of attributes required to create a valid - # Tour. As you add validations to Tour, be sure to - # adjust the attributes here as well. - let(:valid_attributes) do - skip('Add a hash of attributes valid for your model') - end + describe 'GET #index' do + it 'returns a 200 response and empty tour when none found' do + StopSlug.all.each { |t| t.delete } + Stop.all.each { |t| t.delete } + Tour.all.each { |t| t.delete } + get :index, params: { tenant: 'public' } + expect(json).to be_empty + expect(response.status).to eq(200) + end - let(:invalid_attributes) do - skip('Add a hash of attributes invalid for your model') - end + it 'returns a 200 response' do + tour = create(:tour) + tour.update(published: true) + get :index, params: { tenant: tour.tenant } + expect(response.status).to eq(200) + expect(json.count).to eq(Tour.published.count) + end - # This should return the minimal set of values that should be in the session - # in order to pass any filters (e.g. authentication) defined in - # ToursController. Be sure to keep this updated too. - let(:valid_session) { {} } + it 'returns a 200 response when requeted by slug' do + tour = create(:tour) + tour.update(published: true) + get :index, params: { tenant: tour.tenant, slug: tour.slug } + expect(response.status).to eq(200) + expect(attributes[:title]).to eq(tour.title) + end - describe 'GET #index' do - it 'returns a success response' do - Tour.create! valid_attributes - get :index, params: {}, session: valid_session - expect(response).to be_success + # This is for when an authenticated person is viewing an unpublished tour. + # This situation occurs when Ember FastBoot tries to pre-render an unpublished + # tour. FastBoot does not have credentials to send. A 404 response causes + # FastBoot to throw an error and prevents the client from rendering. + it 'returns a 200 response and empty tour when tour is not published' do + tour = create(:tour) + tour.update(published: false) + get :index, params: { tenant: tour.tenant, slug: tour.slug } + expect(response.status).to eq(200) + expect(attributes[:title]).not_to eq(tour.title) + expect(attributes[:title]).to eq('....') + end + + it 'returns a 200 response when request is authenticated by tenant admin and tour is unpublished' do + tour = create(:tour, published: false) + tour.update(published: false) + user = create(:user) + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + signed_cookie(user) + get :index, params: { tenant: tour.tenant, slug: tour.slug } + expect(response.status).to eq(200) + expect(attributes[:title]).to eq(tour.title) + end + + it 'returns a 200 response when request is authenticated by tour author and tour is unpublished' do + tour = create(:tour, published: false) + tour.update(published: false) + user = create(:user) + user.tour_sets = [] + user.tours << tour + signed_cookie(user) + get :index, params: { tenant: tour.tenant, slug: tour.slug } + expect(response.status).to eq(200) + expect(attributes[:title]).to eq(tour.title) + end + + it 'returns all Tour objects when requested by tenant admin' do + create_list(:tour, rand(4..5)) + user = create(:user, super: false) + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + signed_cookie(user) + get :index, params: { tenant: Apartment::Tenant.current } + expect(json.count).to eq(Tour.count) + end + + it 'returns only tours where requester is an author' do + Tour.first.update(published: true) + new_tours = create_list(:tour, rand(4..6), published: false) + user = create(:user, super: false) + user.tour_sets = [] + user.tours << [Tour.published.first, new_tours.first, new_tours.last] + signed_cookie(user) + get :index, params: { tenant: Apartment::Tenant.current } + expect(json.count).to eq((user.tours + Tour.published).uniq.count) + expect(json.count).to be < Tour.count end end describe 'GET #show' do - it 'returns a success response' do - tour = Tour.create! valid_attributes - get :show, params: { id: tour.to_param }, session: valid_session - expect(response).to be_success + it 'returns a 200 response' do + tour = create(:tour, published: false, mode: Mode.find_by(title: 'BICYCLING'), stops: create_list(:stop, rand(5..7))) + tour.update(published: true) + tour.save + get :show, params: { tenant: tour.tenant, id: tour.id } + expect(response.status).to eq(200) + expect(attributes[:title]).to eq(tour.title) + expect(attributes[:est_time]).to eq('About 2 hours bicycling') + end + + # This is for when an authenticated person is viewing an unpublished tour. + # This situation occurs when Ember FastBoot tries to pre-render an unpublished + # tour. FastBoot does not have credentials to send. A 404 response causes + # FastBoot to throw an error and prevents the client from rendering. + it 'returns a 200 response and empty tour when tour is not published' do + tour = create(:tour) + tour.update(published: false) + cookies[:auth] = nil + get :show, params: { tenant: tour.tenant, id: tour.id } + expect(response.status).to eq(200) + expect(attributes[:title]).not_to eq(tour.title) + expect(attributes[:title]).to eq('....') + end + + it 'returns a 200 response when request is authenticated by tour author and tour is unpublished' do + tour = create(:tour, published: false) + tour.update(published: false, media: create_list(:medium, 3)) + user = create(:user) + user.tour_sets = [] + user.tours << tour + signed_cookie(user) + get :show, params: { tenant: tour.tenant, id: tour.id } + expect(response.status).to eq(200) + expect(attributes[:title]).to eq(tour.title) + end + + it 'returns a 200 response when request is authenticated by tenant admin and tour is unpublished' do + tour = create(:tour, published: false, medium: create(:medium)) + tour.update(published: false) + user = create(:user) + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + signed_cookie(user) + get :show, params: { tenant: tour.tenant, id: tour.id } + expect(response.status).to eq(200) + expect(attributes[:title]).to eq(tour.title) + end + + it 'retuns a tour with center lat/lng based on request' do + request.env['ipinfo'] = MockIpinfo.new + tour = create(:tour, stops: []) + user = create(:user, super: true) + signed_cookie(user) + get :show, params: { tenant: Apartment::Tenant.current, id: tour.id } + expect(attributes[:bounds][:centerLat]).not_to eq(33.75432) + expect(attributes[:bounds][:centerLng]).not_to eq(-84.38979) end end describe 'POST #create' do context 'with valid params' do - it 'creates a new Tour' do - expect do - post :create, params: { tour: valid_attributes }, session: valid_session - end.to change(Tour, :count).by(1) + it 'return 401 when unauthenciated' do + post :create, params: { data: { type: 'tours', attributes: { title: 'Burrito Tour' } }, tenant: TourSet.first.subdir } + expect(response.status).to eq(401) end - it 'renders a JSON response with the new tour' do - post :create, params: { tour: valid_attributes }, session: valid_session - expect(response).to have_http_status(:created) - expect(response.content_type).to eq('application/json') - expect(response.location).to eq(tour_url(Tour.last)) + it 'return 401 when authenciated but not an admin for current tenant' do + user = create(:user) + user.update(super: false) + user.tour_sets = [] + signed_cookie(user) + post :create, params: { data: { type: 'tours', attributes: { title: 'Burrito Tour' } }, tenant: TourSet.first.subdir } + expect(response.status).to eq(401) + end + + it 'return 201 when authenciated but an admin for current tenant' do + user = create(:user) + user.update(super: false) + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + signed_cookie(user) + original_tour_count = Tour.count + post :create, params: { data: { type: 'tours', attributes: { title: 'Burrito Tour' } }, tenant: Apartment::Tenant.current } + expect(response.status).to eq(201) + expect(Tour.count).to eq(original_tour_count + 1) + end + + it 'return 201 when authenciated by super' do + user = create(:user) + user.tour_sets = [] + user.update(super: true) + signed_cookie(user) + original_tour_count = Tour.count + post :create, params: { data: { type: 'tours', attributes: { title: 'Burrito Tour' } }, tenant: Apartment::Tenant.current } + expect(response.status).to eq(201) + expect(Tour.count).to eq(original_tour_count + 1) end end context 'with invalid params' do - it 'renders a JSON response with errors for the new tour' do - post :create, params: { tour: invalid_attributes }, session: valid_session - expect(response).to have_http_status(:unprocessable_entity) - expect(response.content_type).to eq('application/json') + it 'returns 422 when invalid attributes' do + user = create(:user, super: true) + signed_cookie(user) + original_tour_count = Tour.count + post :create, params: { data: { type: 'tours', attributes: { title: nil } }, tenant: Apartment::Tenant.current } + expect(response.status).to eq(422) + expect(Tour.count).to eq(original_tour_count) + expect(errors).to include('Title can\'t be blank') + end + + it 'returns 422 when title already used' do + user = create(:user, super: true) + signed_cookie(user) + title = Faker::Movies::HitchhikersGuideToTheGalaxy.location + create(:tour, title: title) + post :create, params: { data: { type: 'tours', attributes: { title: title } }, tenant: Apartment::Tenant.current } + expect(response.status).to eq(422) + expect(Tour.where(title: title).count). to eq(1) end end end describe 'PUT #update' do context 'with valid params' do - let(:new_attributes) do - skip('Add a hash of attributes valid for your model') - end + it 'return 401 when unauthenciated' do + tour = create(:tour, published: false) + post :update, params: { id: tour.id, data: { type: 'tours', attributes: { title: 'Burrito Tour' } }, tenant: TourSet.first.subdir } + expect(response.status).to eq(401) + end - it 'updates the requested tour' do - tour = Tour.create! valid_attributes - put :update, params: { id: tour.to_param, tour: new_attributes }, session: valid_session - tour.reload - skip('Add assertions for updated state') + it 'return 401 when authenciated but not an admin for current tenant' do + tour = create(:tour, published: false) + user = create(:user) + user.update(super: false) + user.tour_sets = [] + signed_cookie(user) + post :update, params: { id: tour.id, data: { type: 'tours', attributes: { title: 'Burrito Tour' } }, tenant: TourSet.first.subdir } + expect(response.status).to eq(401) end - it 'renders a JSON response with the tour' do - tour = Tour.create! valid_attributes + it 'return 200 and updated tour when authenciated but an admin for current tenant' do + tour = create(:tour, published: false) + user = create(:user) + user.update(super: false) + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + signed_cookie(user) + new_title = Faker::Name.unique.name + post :update, params: { id: tour.id, data: { type: 'tours', attributes: { title: new_title } }, tenant: Apartment::Tenant.current } + expect(response.status).to eq(200) + expect(attributes[:title]).not_to eq(tour.title) + expect(attributes[:title]).to eq(new_title) + expect(Tour.find(tour.id).title).to eq(new_title) + end - put :update, params: { id: tour.to_param, tour: valid_attributes }, session: valid_session - expect(response).to have_http_status(:ok) - expect(response.content_type).to eq('application/json') + it 'return 200 and updated tour when authenciated by super' do + tour = create(:tour, published: false) + user = create(:user) + user.tour_sets = [] + user.update(super: true) + signed_cookie(user) + new_title = Faker::Name.unique.name + post :update, params: { id: tour.id, data: { type: 'tours', attributes: { title: new_title } }, tenant: Apartment::Tenant.current } + expect(response.status).to eq(200) + expect(attributes[:title]).not_to eq(tour.title) + expect(attributes[:title]).to eq(new_title) + expect(Tour.find(tour.id).title).to eq(new_title) end - end - context 'with invalid params' do - it 'renders a JSON response with errors for the tour' do - tour = Tour.create! valid_attributes + it 'return 200 and updated tour when authenciated by tour author' do + tour = create(:tour, published: false) + user = create(:user) + user.update(super: false) + user.tour_sets = [] + user.tours << tour + signed_cookie(user) + new_title = Faker::Name.unique.name + post :update, params: { id: tour.id, data: { type: 'tours', attributes: { title: new_title } }, tenant: Apartment::Tenant.current } + expect(response.status).to eq(200) + expect(attributes[:title]).not_to eq(tour.title) + expect(attributes[:title]).to eq(new_title) + expect(Tour.find(tour.id).title).to eq(new_title) + end - put :update, params: { id: tour.to_param, tour: invalid_attributes }, session: valid_session - expect(response).to have_http_status(:unprocessable_entity) - expect(response.content_type).to eq('application/json') + it 'returns 200 and adds stop to a tour' do + tour = create(:tour) + create_list(:stop, 5) + Stop.all.each { |stop| tour.stops << stop } + serialized_tour = JSON.parse(ActiveModelSerializers::Adapter::JsonApi.new(V3::TourSerializer.new(tour)).to_json).with_indifferent_access + original_stop_count = serialized_tour[:data][:relationships][:stops][:data].count + original_tour_stop_count = serialized_tour[:data][:relationships][:tour_stops][:data].count + expect(original_stop_count).to be >= 5 + expect(original_tour_stop_count).to be >= 5 + expect(original_stop_count).to eq(original_tour_stop_count) + expect(original_stop_count).to eq(tour.stops.count) + expect(original_tour_stop_count).to eq(tour.tour_stops.count) + stop = create(:stop) + expect(serialized_tour[:data][:relationships][:stops][:data].map { |s| s['id'] }).not_to include(stop.id.to_s) + serialized_tour[:data][:relationships][:stops][:data].push(JSON.parse("{\"id\":\"#{stop.id}\",\"type\":\"stops\"}")) + expect(serialized_tour[:data][:relationships][:stops][:data].count).to eq(original_stop_count + 1) + expect(serialized_tour[:data][:relationships][:stops][:data].map { |s| s['id'] }).to include(stop.id.to_s) + expect(tour.stops).not_to include(stop) + user = create(:user) + user.update(super: false) + user.tour_sets = [] + user.tours << tour + signed_cookie(user) + post :update, params: { id: tour.id, data: serialized_tour[:data], tenant: Apartment::Tenant.current } + expect(response.status).to eq(200) + expect(Tour.find(tour.id).stops).to include(stop) + expect(relationships[:stops][:data].count).to eq(original_stop_count + 1) + expect(relationships[:tour_stops][:data].count).to eq(original_tour_stop_count + 1) + end + + it 'returns 200 and removes a stop from a tour' do + tour = create(:tour) + create_list(:stop, 5) + Stop.all.each { |stop| tour.stops << stop } + serialized_tour = JSON.parse(ActiveModelSerializers::Adapter::JsonApi.new(V3::TourSerializer.new(tour)).to_json).with_indifferent_access + original_stop_count = serialized_tour[:data][:relationships][:stops][:data].count + original_tour_stop_count = serialized_tour[:data][:relationships][:tour_stops][:data].count + expect(original_stop_count).to be >= 5 + expect(original_tour_stop_count).to be >= 5 + expect(original_stop_count).to eq(original_tour_stop_count) + expect(original_stop_count).to eq(tour.stops.count) + expect(original_tour_stop_count).to eq(tour.tour_stops.count) + expect(serialized_tour[:data][:relationships][:stops][:data].count).to eq(original_stop_count) + stop = serialized_tour[:data][:relationships][:stops][:data].pop + expect(serialized_tour[:data][:relationships][:stops][:data].map { |s| s['id'] }).not_to include(stop[:id]) + expect(serialized_tour[:data][:relationships][:stops][:data].count).to eq(original_stop_count - 1) + expect(tour.stops).to include(Stop.find(stop[:id])) + user = create(:user) + user.update(super: false) + user.tour_sets = [] + user.tours << tour + signed_cookie(user) + post :update, params: { id: tour.id, data: serialized_tour[:data], tenant: Apartment::Tenant.current } + expect(response.status).to eq(200) + expect(Tour.find(tour.id).stops).not_to include(Stop.find(stop[:id])) + expect(relationships[:stops][:data].count).to eq(original_stop_count - 1) + expect(relationships[:tour_stops][:data].count).to eq(original_tour_stop_count - 1) + end + + it 'returns 422 when title in nil' do + tour = create(:tour) + serialized_tour = JSON.parse(ActiveModelSerializers::Adapter::JsonApi.new(V3::TourSerializer.new(tour)).to_json).with_indifferent_access + serialized_tour[:data][:attributes][:title] = nil + user = create(:user, super: true) + signed_cookie(user) + post :update, params: { id: tour.id, data: serialized_tour[:data], tenant: Apartment::Tenant.current } + expect(response.status).to eq(422) + expect(errors).to include('Title can\'t be blank') end end end describe 'DELETE #destroy' do - it 'destroys the requested tour' do - tour = Tour.create! valid_attributes - expect do - delete :destroy, params: { id: tour.to_param }, session: valid_session - end.to change(Tour, :count).by(-1) + it 'return 401 when unauthenciated' do + tour = create(:tour, published: false) + post :destroy, params: { id: tour.id, tenant: TourSet.first.subdir } + expect(response.status).to eq(401) + end + + it 'return 401 when authenciated but not an admin for current tenant' do + tour = create(:tour, published: false) + user = create(:user) + user.update(super: false) + user.tour_sets = [] + signed_cookie(user) + post :destroy, params: { id: tour.id, tenant: TourSet.first.subdir } + expect(response.status).to eq(401) + end + + it 'return 204 and one less tour when authenciated but an admin for current tenant' do + tour = create(:tour, published: false) + user = create(:user) + user.update(super: false) + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + signed_cookie(user) + tour_count = Tour.count + post :destroy, params: { id: tour.id, tenant: Apartment::Tenant.current } + expect(response.status).to eq(204) + expect(Tour.count).to eq(tour_count - 1) + end + + it 'return 204 and one less tour when authenciated by super' do + tour = create(:tour, published: false) + user = create(:user) + user.tour_sets = [] + user.update(super: true) + signed_cookie(user) + tour_count = Tour.count + post :destroy, params: { id: tour.id, tenant: Apartment::Tenant.current } + expect(response.status).to eq(204) + expect(Tour.count).to eq(tour_count - 1) + end + + it 'return 204 and one less tour when authenciated by tour author' do + tour = create(:tour, published: false) + user = create(:user) + user.update(super: false) + user.tour_sets = [] + user.tours << tour + signed_cookie(user) + tour_count = Tour.count + post :destroy, params: { id: tour.id, tenant: Apartment::Tenant.current } + expect(response.status).to eq(204) + expect(Tour.count).to eq(tour_count - 1) end end end diff --git a/spec/controllers/v3/users_controller_spec-fix.rb b/spec/controllers/v3/users_controller_spec-fix.rb deleted file mode 100644 index eef9da37..00000000 --- a/spec/controllers/v3/users_controller_spec-fix.rb +++ /dev/null @@ -1,127 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -# This spec was generated by rspec-rails when you ran the scaffold generator. -# It demonstrates how one might use RSpec to specify the controller code that -# was generated by Rails when you ran the scaffold generator. -# -# It assumes that the implementation code is generated by the rails scaffold -# generator. If you are using any extension libraries to generate different -# controller code, this generated spec may or may not pass. -# -# It only uses APIs available in rails and/or rspec-rails. There are a number -# of tools you can use to make these specs even more expressive, but we're -# sticking to rails and rspec-rails APIs to keep things simple and stable. -# -# Compared to earlier versions of this generator, there is very limited use of -# stubs and message expectations in this spec. Stubs are only used when there -# is no simpler way to get a handle on the object needed for the example. -# Message expectations are only used when there is no simpler way to specify -# that an instance is receiving a specific message. -# -# Also compared to earlier versions of this generator, there are no longer any -# expectations of assigns and templates rendered. These features have been -# removed from Rails core in Rails 5, but can be added back in via the -# `rails-controller-testing` gem. - -RSpec.describe V3::UsersController, type: :controller do - # This should return the minimal set of attributes required to create a valid - # User. As you add validations to User, be sure to - # adjust the attributes here as well. - let(:valid_attributes) do - skip('Add a hash of attributes valid for your model') - end - - let(:invalid_attributes) do - skip('Add a hash of attributes invalid for your model') - end - - # This should return the minimal set of values that should be in the session - # in order to pass any filters (e.g. authentication) defined in - # UsersController. Be sure to keep this updated too. - let(:valid_session) { {} } - - describe 'GET #index' do - it 'returns a success response' do - # user = User.create! valid_attributes - get :index, params: {}, session: valid_session - expect(response).to be_success - end - end - - describe 'GET #show' do - it 'returns a success response' do - user = User.create! valid_attributes - get :show, params: { id: user.to_param }, session: valid_session - expect(response).to be_success - end - end - - describe 'POST #create' do - context 'with valid params' do - it 'creates a new User' do - expect do - post :create, params: { user: valid_attributes }, session: valid_session - end.to change(User, :count).by(1) - end - - it 'renders a JSON response with the new user' do - post :create, params: { user: valid_attributes }, session: valid_session - expect(response).to have_http_status(:created) - expect(response.content_type).to eq('application/json') - expect(response.location).to eq(user_url(User.last)) - end - end - - context 'with invalid params' do - it 'renders a JSON response with errors for the new user' do - post :create, params: { user: invalid_attributes }, session: valid_session - expect(response).to have_http_status(:unprocessable_entity) - expect(response.content_type).to eq('application/json') - end - end - end - - describe 'PUT #update' do - context 'with valid params' do - let(:new_attributes) do - skip('Add a hash of attributes valid for your model') - end - - it 'updates the requested user' do - user = User.create! valid_attributes - put :update, params: { id: user.to_param, user: new_attributes }, session: valid_session - user.reload - skip('Add assertions for updated state') - end - - it 'renders a JSON response with the user' do - user = User.create! valid_attributes - - put :update, params: { id: user.to_param, user: valid_attributes }, session: valid_session - expect(response).to have_http_status(:ok) - expect(response.content_type).to eq('application/json') - end - end - - context 'with invalid params' do - it 'renders a JSON response with errors for the user' do - user = User.create! valid_attributes - - put :update, params: { id: user.to_param, user: invalid_attributes }, session: valid_session - expect(response).to have_http_status(:unprocessable_entity) - expect(response.content_type).to eq('application/json') - end - end - end - - describe 'DELETE #destroy' do - it 'destroys the requested user' do - user = User.create! valid_attributes - expect do - delete :destroy, params: { id: user.to_param }, session: valid_session - end.to change(User, :count).by(-1) - end - end -end diff --git a/spec/controllers/v3/users_controller_spec.rb b/spec/controllers/v3/users_controller_spec.rb new file mode 100644 index 00000000..872f7425 --- /dev/null +++ b/spec/controllers/v3/users_controller_spec.rb @@ -0,0 +1,279 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe V3::UsersController, type: :controller do + describe 'GET #index' do + context 'unauthorized' do + it 'returns a success response but empty json when request is unauthenticated' do + create_list(:user, rand(4..5)) + get :index, params: { tenant: Apartment::Tenant.current } + expect(response.status).to eq(200) + expect(json).to be_empty + end + + it 'returns a success response but empty json when request is unauthenticated' do + create_list(:user, rand(4..5)) + user = User.last + user.update(super: false) + signed_cookie(user) + get :index, params: { tenant: Apartment::Tenant.current } + expect(response.status).to eq(200) + expect(json).to be_empty + end + end + + context 'authorized' do + it 'returns current user when requested by current user' do + user = create(:user) + signed_cookie(user) + get :index, params: { tenant: Apartment::Tenant.current, me: true } + expect(response.status).to eq(200) + expect(json[:id]).to eq(user.id.to_s) + end + + it 'returns list of users when requested by super' do + create_list(:user, rand(4..7)) + User.all.each {|user| user.tours << create_list(:tour, rand(0..3))} + user = User.last + user.update(super: true) + signed_cookie(user) + get :index, params: { tenant: Apartment::Tenant.current } + expect(json.count).to eq(User.count) + expect(User.all.map(&:all_tours).all? { |tours| tours.empty? }).not_to be true + expect(json.map { |user| user[:attributes][:all_tours].empty? }).to all(be true) + end + end + end + + describe 'GET #show' do + let(:user) { create(:user, super: false) } + let(:other_user) { create(:user, super: false) } + + context 'unauthorized' do + it 'returns 401 when unauthenticated' do + signed_cookie(user) + get :show, params: { id: other_user.to_param, tenant: Apartment::Tenant.current } + expect(response.status).to eq(401) + end + + it 'returns 401 when unauthorized tenant admin' do + user.tour_sets << create(:tour_set) + signed_cookie(user) + get :show, params: { id: other_user.to_param, tenant: Apartment::Tenant.current } + expect(response.status).to eq(401) + end + + it 'returns 401 when unauthorized tour author' do + user.tours << create(:tour) + signed_cookie(user) + get :show, params: { id: other_user.to_param, tenant: Apartment::Tenant.current } + expect(response.status).to eq(401) + end + end + + context 'authorized' do + it 'returns user when requested by self' do + signed_cookie(user) + get :show, params: { id: user.to_param, tenant: Apartment::Tenant.current } + expect(response.status).to eq(200) + expect(json[:id]).to eq(user.id.to_s) + end + + it 'returns user when requested by super' do + user.update(super: true) + other_user.tours << create_list(:tour, 3) + signed_cookie(user) + get :show, params: { id: other_user.to_param, tenant: Apartment::Tenant.current } + expect(response.status).to eq(200) + expect(json[:id]).to eq(other_user.id.to_s) + expect(json[:attributes][:all_tours].count).to eq(3) + end + + end + end + + describe 'POST #create' do + let(:user) { create(:user, super: false) } + let(:valid_params) { { data: { type: 'users', attributes: { display_name: Faker::Music::Hiphop.artist, email: Faker::Internet.email } }, tenant: 'public' } } + + context 'unauthorized' do + it 'does not create a new User when unauthenticated' do + expect do + post :create, params: valid_params + end.to change(User, :count).by(0) + end + + it 'does not create a new User for tenant admin' do + user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) + signed_cookie(user) + expect do + post :create, params: valid_params + end.to change(User, :count).by(0) + end + + it 'does not create a new User for tour author' do + user.tours << create(:tour) + signed_cookie(user) + expect do + post :create, params: valid_params + end.to change(User, :count).by(0) + end + end + + context 'authorized' do + it 'creates a new User for super' do + user.update(super: true) + signed_cookie(user) + expect do + post :create, params: valid_params + end.to change(User, :count).by(1) + end + + it 'does not create a new User for super with invalid_params' do + user.update(super: true) + valid_params[:data][:attributes].delete(:email) + signed_cookie(user) + expect do + post :create, params: valid_params + end.to change(User, :count).by(0) + end + + it 'responds with errors when creating a new User for super with invalid_params' do + user.update(super: true) + valid_params[:data][:attributes].delete(:email) + signed_cookie(user) + post :create, params: valid_params + expect(response.status).to eq(422) + expect(errors).to include('Email can\'t be blank') + end + end + end + + describe 'PUT #update' do + context 'unauthorized' do + it 'does not update when unauthenticated' do + user = create(:user, super: false) + initial_display_name = user.display_name + new_display_name = Faker::Music::Hiphop.artist + update_params = { id: user.to_param, tenant: 'public', data: { type: 'users', attributes: { display_name: new_display_name } } } + put :update, params: update_params + expect(response.status).to eq(401) + user.reload + expect(user.display_name).not_to eq(new_display_name) + end + + it 'does not update when authenticated as user not the one being updated' do + user = create(:user, super: false) + user_to_update = create(:user) + user.tour_sets << create(:tour_set) + initial_display_name = user_to_update.display_name + new_display_name = Faker::Music::Hiphop.artist + update_params = { id: user_to_update.to_param, tenant: 'public', data: { type: 'users', attributes: { display_name: new_display_name } } } + signed_cookie(user) + put :update, params: update_params + expect(response.status).to eq(401) + user_to_update.reload + expect(user_to_update.display_name).not_to eq(new_display_name) + end + + it 'does not update when authenticated by tenant admin' do + user = create(:user) + user_to_update = create(:user) + user.tour_sets << create(:tour_set) + initial_display_name = user_to_update.display_name + new_display_name = "#{Faker::Music::Hiphop.artist}!" + update_params = { id: user_to_update.to_param, tenant: 'public', data: { type: 'users', attributes: { display_name: new_display_name } } } + signed_cookie(user) + put :update, params: update_params + expect(response.status).to eq(401) + user_to_update.reload + expect(user_to_update.display_name).not_to eq(new_display_name) + end + + it 'does not update when authenticated by tour author' do + user = create(:user) + user_to_update = create(:user) + user.tours << create(:tour) + initial_display_name = user_to_update.display_name + new_display_name = Faker::Music::Hiphop.artist + update_params = { id: user_to_update.to_param, tenant: 'public', data: { type: 'users', attributes: { display_name: new_display_name } } } + signed_cookie(user) + put :update, params: update_params + expect(response.status).to eq(401) + user_to_update.reload + expect(user_to_update.display_name).not_to eq(new_display_name) + end + end + + context 'authorized' do + it 'updates user when requested by self' do + user = create(:user) + initial_display_name = Faker::Music::Hiphop.artist + new_display_name = Faker::Music::Hiphop.artist + update_params = { id: user.to_param, tenant: 'public', data: { type: 'users', attributes: { display_name: new_display_name } } } + signed_cookie(user) + put :update, params: update_params + expect(response.status).to eq(200) + user.reload + expect(user.display_name).to eq(new_display_name) + end + + it 'updates user when requested by super' do + user = create(:user, super: true) + user_to_update = create(:user) + initial_display_name = user_to_update.display_name + new_display_name = Faker::Music::Hiphop.artist + update_params = { id: user_to_update.to_param, tenant: 'public', data: { type: 'users', attributes: { display_name: new_display_name } } } + signed_cookie(user) + put :update, params: update_params + expect(response.status).to eq(200) + user_to_update.reload + expect(user_to_update.display_name).to eq(new_display_name) + end + + it 'returns 422 when email in nil' do + user = create(:user, super: true) + user_to_update = create(:user) + initial_email = user_to_update.email + update_params = { id: user_to_update.to_param, tenant: 'public', data: { type: 'users', attributes: { email: nil } } } + signed_cookie(user) + put :update, params: update_params + expect(response.status).to eq(422) + expect(errors).to include('Email can\'t be blank') + user_to_update.reload + expect(user_to_update.email).to eq(initial_email) + end + end + end + + describe 'DELETE #destroy' do + context 'unauthorized' do + it 'does not destroy the requested user when unauthenticated' do + user = create(:user) + expect do + delete :destroy, params: { id: user.to_param, tenant: 'public' } + end.to change(User, :count).by(0) + end + + it 'does not destroy the requested user when authenticated' do + user = create(:user) + signed_cookie(user) + expect do + delete :destroy, params: { id: user.to_param, tenant: 'public' } + end.to change(User, :count).by(0) + end + end + + context 'authorized' do + it 'destroys user when requested by super' do + super_user = create(:user, super: true) + user = create(:user) + signed_cookie(super_user) + expect do + delete :destroy, params: { id: user.to_param, tenant: 'public' } + end.to change(User, :count).by(-1) + end + end + end +end \ No newline at end of file diff --git a/spec/factories/distance_matrix.json b/spec/factories/distance_matrix.json new file mode 100644 index 00000000..11332b2b --- /dev/null +++ b/spec/factories/distance_matrix.json @@ -0,0 +1,42 @@ +{ + "destination_addresses": ["Lexington, MA, USA", "Concord, MA, USA"], + "origin_addresses": ["Boston, MA, USA", "Charlestown, Boston, MA, USA"], + "rows": + [ + { + "elements": + [ + { + "distance": { "text": "23.6 km", "value": 23644 }, + "duration": { "text": "28 mins", "value": 1673 }, + "duration_in_traffic": { "text": "34 mins", "value": 2026 }, + "status": "OK" + }, + { + "distance": { "text": "41.3 km", "value": 41294 }, + "duration": { "text": "34 mins", "value": 2063 }, + "duration_in_traffic": { "text": "37 mins", "value": 2231 }, + "status": "OK" + } + ] + }, + { + "elements": + [ + { + "distance": { "text": "31.2 km", "value": 31219 }, + "duration": { "text": "26 mins", "value": 1545 }, + "duration_in_traffic": { "text": "32 mins", "value": 1942 }, + "status": "OK" + }, + { + "distance": { "text": "29.6 km", "value": 29594 }, + "duration": { "text": "32 mins", "value": 1895 }, + "duration_in_traffic": { "text": "37 mins", "value": 2218 }, + "status": "OK" + } + ] + } + ], + "status": "OK" +} \ No newline at end of file diff --git a/spec/factories/distance_matrix2.json b/spec/factories/distance_matrix2.json new file mode 100755 index 00000000..1cf1a558 --- /dev/null +++ b/spec/factories/distance_matrix2.json @@ -0,0 +1,42 @@ +{ + "destination_addresses": ["Lexington, MA, USA", "Concord, MA, USA"], + "origin_addresses": ["Boston, MA, USA", "Charlestown, Boston, MA, USA"], + "rows": + [ + { + "elements": + [ + { + "distance": { "text": "23.6 km", "value": 23644 }, + "duration": { "text": "28 mins", "value": 1673 }, + "duration_in_traffic": { "text": "34 mins", "value": 2026 }, + "status": "OK" + }, + { + "distance": { "text": "41.3 km", "value": 41294 }, + "duration": { "text": "34 mins", "value": 1063 }, + "duration_in_traffic": { "text": "37 mins", "value": 2231 }, + "status": "OK" + } + ] + }, + { + "elements": + [ + { + "distance": { "text": "31.2 km", "value": 31219 }, + "duration": { "text": "26 mins", "value": 1545 }, + "duration_in_traffic": { "text": "32 mins", "value": 1942 }, + "status": "OK" + }, + { + "distance": { "text": "29.6 km", "value": 29594 }, + "duration": { "text": "32 mins", "value": 895 }, + "duration_in_traffic": { "text": "37 mins", "value": 2218 }, + "status": "OK" + } + ] + } + ], + "status": "OK" +} \ No newline at end of file diff --git a/spec/factories/distance_matrix_zero.json b/spec/factories/distance_matrix_zero.json new file mode 100644 index 00000000..b8da0655 --- /dev/null +++ b/spec/factories/distance_matrix_zero.json @@ -0,0 +1,16 @@ +{ + "destination_addresses": ["Lexington, MA, USA", "Concord, MA, USA"], + "origin_addresses": ["Boston, MA, USA", "Charlestown, Boston, MA, USA"], + "rows": + [ + { + "elements": + [ + { + "status": "ZERO_RESULTS" + } + ] + } + ], + "status": "OK" +} \ No newline at end of file diff --git a/spec/factories/flat_pages.rb b/spec/factories/flat_pages.rb index 6f5dcb36..220f4fa0 100644 --- a/spec/factories/flat_pages.rb +++ b/spec/factories/flat_pages.rb @@ -4,6 +4,6 @@ FactoryBot.define do factory :flat_page do title { Faker::Movies::HitchhikersGuideToTheGalaxy.planet } - content { Faker::Hipster.paragraph(sentence_count: 2, supplemental: true, random_sentences_to_add: 4) } + body { Faker::Hipster.paragraph(sentence_count: 2, supplemental: true, random_sentences_to_add: 4) } end end diff --git a/spec/factories/images/0.jpg b/spec/factories/images/0.jpg index a386aac0..2a5abaa2 100644 Binary files a/spec/factories/images/0.jpg and b/spec/factories/images/0.jpg differ diff --git a/spec/factories/images/atl.png b/spec/factories/images/atl.png new file mode 100644 index 00000000..981239af Binary files /dev/null and b/spec/factories/images/atl.png differ diff --git a/spec/factories/images/atl_base64.txt b/spec/factories/images/atl_base64.txt new file mode 100644 index 00000000..696717e1 --- /dev/null +++ b/spec/factories/images/atl_base64.txt @@ -0,0 +1 @@ +data:image/jpeg;base64,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 \ No newline at end of file diff --git a/spec/factories/images/gif_base64.txt b/spec/factories/images/gif_base64.txt new file mode 100644 index 00000000..6e32523c --- /dev/null +++ b/spec/factories/images/gif_base64.txt @@ -0,0 +1 @@ +data:image/gif;base64,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 \ No newline at end of file diff --git a/spec/factories/images/giphy.gif b/spec/factories/images/giphy.gif new file mode 100644 index 00000000..dbb996e8 Binary files /dev/null and b/spec/factories/images/giphy.gif differ diff --git a/spec/factories/images/icon_base64.txt b/spec/factories/images/icon_base64.txt new file mode 100644 index 00000000..e4d9313f --- /dev/null +++ b/spec/factories/images/icon_base64.txt @@ -0,0 +1 @@ +data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADgAAAA4CAMAAACfWMssAAAACXBIWXMAAAAcAAAAHAAPAbmPAAAAt1BMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAD////////Q0NDr6+t2dnYAAAD39/daWlqVlZX///99fX3////////////39/fMzMz7+/vr6+v////////v7+////////////8YGBj///////////+xsbGJiYn////MzMz////7+/v36+vMhXqtNyelKxjUnZWxQzP37++lJxS1Tz/cqaHz4Nzgta2pMx+9YlbYpZ3EdmrjubWpLx+5VkfMiX3elJSEAAAAJ3RSTlMATz8SKxhHkcy11GoO62qBJ3ZqS7nzrffcL/fcxO/cVgozN5VHP4FzqteCAAACuUlEQVR42u39RcPjIBCGAUA8Uq/XPp9Yvd7+/9+1lBgpsW/3spf3lsCDvMzAHNF/omZb7c57tt2bd9V2syo1UmuQUk0dVcA6U9LVObinlef73urkHhzyY9opwd41gPX5vknpfl4DaO8F2JtKJnNvG043l0yrvuVxTQ3sZQZG0aUNWo5NHzPYbze52u5h9pHFdcZwPW4KdLzCOMOj7zHs/E2h/B2Mv1+55gx2m1LtYPayzzcNrn456F9BS3urwv64qaDjHlSWeweb93O739/5nzawkaDBku9CwsXmySVoCdcBhzt3sqhn0Hqv/28OJGcyBZcb+RxkBm+1C9OIG8Gam3BlhznF7f22hlFs6TlvQshsioytAe+BE4EO13SHWsA1U40n6q+X5D+1x72kBg3Cpw0HhrPp4pYJuAw+GfIA7XCLiaeXpycH31sn4NojwU2ONCHdcJNdOMX/Ai8vF2AUftnJqqBLwTms4n+QD0LcaQVzCvbA+x3oQY+CNvi/A32w/w3866XmmbPe7daZYGSOwRwHAzpkuJWTBZ7AoKDOBAAD0tBbZoEu6BT8YUKOAR/P70cWeIAfCn4xQV4JdOCLgjKTVlVAklYyBa1+kq1VwDP0rSAhzeTqqACSq8MMOCQZsa9bquNxu13RqPBWwddTYRcXDCkExUZ8PS4LFE7oQEMMQSQMowuZSWBO4dAwFCIOyWb0BJSC5Akw5RhEgl790dGTCckuF5Oqz9xkITIgkupVH9a6hFISWoMqT/mgJaQ5ZCmNQXnxMGgo1guIRNwqL1daWEScZFyfFBdIkzqWUYZEZaEXlWT6QhFRpiwBm8O8InBoYsFCeZIwbhiQUXYaDYwlVCBRwNjsvxa6fRNjQUTFkgmK65+6UXuW1jVD/6yTH4KMyiVKCk5JkURUUZYsCQrBFUWQ5GxL/gBT8HlUSJpOxQAAAABJRU5ErkJggg== \ No newline at end of file diff --git a/spec/factories/images/jp2_base64.txt b/spec/factories/images/jp2_base64.txt new file mode 100644 index 00000000..01750512 --- /dev/null +++ b/spec/factories/images/jp2_base64.txt @@ -0,0 +1 @@ +data:image/jp2;base64,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 \ No newline at end of file diff --git a/spec/factories/images/mapicon.png b/spec/factories/images/mapicon.png new file mode 100644 index 00000000..2d989b4d Binary files /dev/null and b/spec/factories/images/mapicon.png differ diff --git a/spec/factories/images/png_base64.txt b/spec/factories/images/png_base64.txt new file mode 100644 index 00000000..709c788c --- /dev/null +++ b/spec/factories/images/png_base64.txt @@ -0,0 +1 @@ +data:image/png;base64,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 \ No newline at end of file diff --git a/spec/factories/test.jpg b/spec/factories/images/test.jpg similarity index 100% rename from spec/factories/test.jpg rename to spec/factories/images/test.jpg diff --git a/spec/factories/login.rb b/spec/factories/login.rb index fb0d8b9f..4d7ba13c 100644 --- a/spec/factories/login.rb +++ b/spec/factories/login.rb @@ -1,16 +1,12 @@ # frozen_string_literal: true # spec/factories/login.rb -FactoryBot.define do - factory :login do - identification { Faker::Internet.email } - oauth2_token { Faker::Omniauth.google[:credentials][:token] } - uid { Faker::Number.number } - provider { 'google' } +require 'faker' +require 'jwt' - after(:create) do |login| - login.user.display_name = Faker::Name.name - login.user.save - end +FactoryBot.define do + factory :login, class: EcdsRailsAuthEngine::Login do + provider { Faker::Internet.domain_name } + user_id { nil } end end diff --git a/spec/factories/map_icons.rb b/spec/factories/map_icons.rb new file mode 100755 index 00000000..f65c076c --- /dev/null +++ b/spec/factories/map_icons.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +# spec/factories/map_icons.rb +FactoryBot.define do + factory :map_icon do + filename { Faker::File.file_name(dir: '', ext: 'png', directory_separator: '') } + base_sixty_four { File.read(Rails.root.join('spec/factories/images/icon_base64.txt')) } + created_at { Faker::Number.number(digits: 10) } + end +end diff --git a/spec/factories/map_overlays.rb b/spec/factories/map_overlays.rb new file mode 100755 index 00000000..345b9038 --- /dev/null +++ b/spec/factories/map_overlays.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +# spec/factories/map_icons.rb +FactoryBot.define do + factory :map_overlay do + south { nil } + east { nil } + north { nil } + west { nil } + filename { Faker::File.file_name(dir: '', ext: 'png', directory_separator: '') } + base_sixty_four { File.read(Rails.root.join('spec/factories/base64_image.txt')) } + association :tour + end +end diff --git a/spec/factories/media.rb b/spec/factories/media.rb index 4b81b8e7..f85290f5 100644 --- a/spec/factories/media.rb +++ b/spec/factories/media.rb @@ -5,8 +5,10 @@ factory :medium do title { Faker::TvShows::RickAndMorty.character } caption { Faker::TvShows::RickAndMorty.quote } - # original_image { Rack::Test::UploadedFile.new(Rails.root.join('spec', 'support', 'images', 'otblogo.png'), 'image/png') } - remote_original_image_url { Faker::Placeholdit.image } + filename { Faker::File.file_name(dir: '', ext: 'png', directory_separator: '') } + base_sixty_four { File.read(Rails.root.join('spec/factories/base64_image.txt')) } created_at { Faker::Number.number(digits: 10) } + video_provider { 'keiner' } + video { nil } end end diff --git a/spec/factories/stop_media.rb b/spec/factories/stop_media.rb index 96f74cf2..1d899d27 100644 --- a/spec/factories/stop_media.rb +++ b/spec/factories/stop_media.rb @@ -3,7 +3,8 @@ # spec/factories/stop_media.rb FactoryBot.define do factory :stop_medium do - stop_id { nil } - medium_id { nil } + association :stop + association :medium + position { 1 } end end diff --git a/spec/factories/stops.rb b/spec/factories/stops.rb index 8707f2bb..677a4713 100644 --- a/spec/factories/stops.rb +++ b/spec/factories/stops.rb @@ -4,6 +4,7 @@ FactoryBot.define do factory :stop do sequence :title do |s| + # This ensures unique titles "#{Faker::Movies::HitchhikersGuideToTheGalaxy.planet}#{s}" end description { Faker::Hipster.paragraph(sentence_count: 2, supplemental: true, random_sentences_to_add: 4) } @@ -18,7 +19,7 @@ # https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md#transient-attributes after(:create) do |stop, evaluator| - create_list(:medium, evaluator.media_count, stops: [stop]) + # create_list(:medium, evaluator.media_count, stops: [stop]) end end end diff --git a/spec/factories/token.rb b/spec/factories/token.rb new file mode 100755 index 00000000..a3093281 --- /dev/null +++ b/spec/factories/token.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +# spec/factories/login.rb +require 'faker' +require 'jwt' + +FactoryBot.define do + factory :token, class: EcdsRailsAuthEngine::Token do + token { JWT.encode(Faker::Beer.style, Faker::Address.zip, 'HS256') } + end +end diff --git a/spec/factories/tour_authors.rb b/spec/factories/tour_authors.rb index 5d4198aa..723c5f08 100644 --- a/spec/factories/tour_authors.rb +++ b/spec/factories/tour_authors.rb @@ -1,7 +1,9 @@ # frozen_string_literal: true -# spec/factories/tour_sets.rb +# spec/factories/tour_authors.rb FactoryBot.define do - factory :tour_authors do + factory :tour_author do + association :tour + association :user end end diff --git a/spec/factories/tour_flat_pages.rb b/spec/factories/tour_flat_pages.rb new file mode 100755 index 00000000..333dc389 --- /dev/null +++ b/spec/factories/tour_flat_pages.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +# spec/factories/tour_flat_pages.rb +FactoryBot.define do + factory :tour_flat_page do + association :tour + association :flat_page + end +end diff --git a/spec/factories/tour_media.rb b/spec/factories/tour_media.rb new file mode 100644 index 00000000..937b7656 --- /dev/null +++ b/spec/factories/tour_media.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +# spec/factories/tour_media.rb +FactoryBot.define do + factory :tour_medium do + association :tour + association :medium + position { 1 } + end +end diff --git a/spec/factories/tour_sets.rb b/spec/factories/tour_sets.rb index 2119d116..39bd751e 100644 --- a/spec/factories/tour_sets.rb +++ b/spec/factories/tour_sets.rb @@ -5,5 +5,7 @@ factory :tour_set do # This is to really make sure the name of the tenant is unique. name { Faker::Name.unique.name } + base_sixty_four { nil } + logo_title { nil } end end diff --git a/spec/factories/tour_stops.rb b/spec/factories/tour_stops.rb index 4a8bab19..5f9a5bf3 100644 --- a/spec/factories/tour_stops.rb +++ b/spec/factories/tour_stops.rb @@ -5,5 +5,6 @@ factory :tour_stop do tour_id { nil } stop_id { nil } + position { nil } end end diff --git a/spec/factories/tours.rb b/spec/factories/tours.rb index 4b9fe1bb..600f404c 100644 --- a/spec/factories/tours.rb +++ b/spec/factories/tours.rb @@ -6,8 +6,10 @@ title { Faker::Name.unique.name } description { "

#{Faker::TvShows::RickAndMorty.quote}

#{Faker::TvShows::RickAndMorty.quote}

#{Faker::TvShows::RickAndMorty.quote}

" } published { Faker::Boolean.boolean(true_ratio: 0.5) } - theme { Theme.create! } - mode { Mode.create! } + theme { create(:theme) } + mode { Mode.first } + link_address { Faker::Internet.url } + is_geo { true } factory :tour_with_stops do transient do @@ -16,7 +18,7 @@ # https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md#transient-attributes after(:create) do |tour, evaluator| - create_list(:stop, evaluator.stops_count, tours: [tour]) + create_list(:tour_stop, evaluator.stops_count, tour: tour, stop: create(:stop)) end end diff --git a/spec/factories/users.rb b/spec/factories/users.rb index 88b19c1e..97e4cb7a 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -3,7 +3,7 @@ # spec/factories/users.rb FactoryBot.define do factory :user do - login { create(:login) } - # tour_sets { TourSet.where(subdir: 'atlanta') } + email { Faker::Internet.email } + display_name { Faker::Music::Hiphop.artist } end end diff --git a/spec/models/map_icon_spec.rb b/spec/models/map_icon_spec.rb new file mode 100644 index 00000000..5ab515b1 --- /dev/null +++ b/spec/models/map_icon_spec.rb @@ -0,0 +1,13 @@ +require 'rails_helper' + +RSpec.describe MapIcon, type: :model do + context 'size error' do + it 'fails validation when image it too big' do + icon = MapIcon.create( + base_sixty_four: File.read(Rails.root.join('spec/factories/images/png_base64.txt')), + filename: Faker::File.file_name(dir: '', ext: 'png', directory_separator: '') + ) + expect(icon.errors.full_messages).to include 'Icons should be no bigger that 80 by 80 pixels' + end + end +end diff --git a/spec/models/map_overlay_spec.rb b/spec/models/map_overlay_spec.rb new file mode 100644 index 00000000..bc869cd2 --- /dev/null +++ b/spec/models/map_overlay_spec.rb @@ -0,0 +1,14 @@ +require 'rails_helper' + +RSpec.describe MapOverlay, type: :model do + it 'has nil values for south, east, north, and west' do + mo = create(:map_overlay, tour: create(:tour, stops: [])) + expect([mo.south, mo.east, mo.north, mo.west]).to all(be nil) + end + + it 'has values for south, east, north, and west based on tour stops' do + tour = create(:tour, stops: create_list(:stop, 3)) + mo = create(:map_overlay, tour: tour) + expect([mo.south, mo.east, mo.north, mo.west]).to all(be_a String) + end +end diff --git a/spec/models/medium_spec.rb b/spec/models/medium_spec.rb index 24651ce0..e6df5afb 100644 --- a/spec/models/medium_spec.rb +++ b/spec/models/medium_spec.rb @@ -5,4 +5,105 @@ RSpec.describe Medium, type: :model do it { should have_many(:stop_media) } it { should have_many(:stops) } + + context 'video' do + it 'gets image from youtube and sets embed' do + medium = create(:medium, video: 'F9ULbmCvmxY', base_sixty_four: nil, video_provider: 'youtube') + expect(medium.embed).to eq("//www.youtube.com/embed/#{medium.video}") + expect(medium.file.attached?).to be true + end + + it 'gets image from youtube when downloaded image is a StrinIO object and sets embed' do + file = File.open(Rails.root + 'spec/factories/images/atl.png') + string_io = StringIO.new(file.read) + base64 = VideoProps.encode_image(string_io) + medium = create(:medium, base_sixty_four: base64) + expect(medium.file.attached?).to be true + end + + it 'gets nothing when YouTube video is not found' do + medium = create(:medium, video: 'CvmxYF9ULbm', base_sixty_four: nil, video_provider: 'youtube') + expect(medium.embed).to be nil + expect(medium.provider).to be nil + expect(medium.file.attached?).to be false + end + + it 'gets image from vimeo and sets embed' do + medium = create(:medium, video: '310645255', base_sixty_four: nil, video_provider: 'vimeo') + expect(medium.embed).to eq("//player.vimeo.com/video/#{medium.video}") + expect(medium.file.attached?).to be true + end + + it 'gets image from soundcloud and sets embed' do + iframe = '
FiendBassy · Boca Raton (with A$AP Ferg)
' + medium = create(:medium, video: iframe, base_sixty_four: nil, video_provider: 'soundcloud') + expect(medium.embed).to eq("//w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/#{medium.video}&color=%23ff5500&auto_play=false&hide_related=true&show_comments=false&show_user=false&show_reposts=false&show_teaser=false&visual=true&sharing=false") + expect(medium.file.attached?).to be true + expect(medium.title).to eq('FiendBassy: Boca Raton (with A$AP Ferg)') + end + + it 'gets default image from when no image found for soundcloud and sets embed' do + iframe = '
Emory Center for Digital Scholarship · Subsatellite Launch
' + medium = create(:medium, video: iframe, base_sixty_four: nil, video_provider: 'soundcloud') + expect(medium.embed).to eq("//w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/#{medium.video}&color=%23ff5500&auto_play=false&hide_related=true&show_comments=false&show_user=false&show_reposts=false&show_teaser=false&visual=true&sharing=false") + expect(medium.file.attached?).to be true + expect(medium.title).to eq('Emory Center for Digital Scholarship') + end + + it 'replaces file for video' do + medium = create(:medium, video: 'F9ULbmCvmxY', base_sixty_four: nil, video_provider: 'youtube') + original_checksum = medium.file.blob.checksum + expect(original_checksum).to eq(Digest::MD5.file(Rails.root.join('spec/factories/images/0.jpg')).base64digest) + medium.update(base_sixty_four: File.read(Rails.root.join('spec/factories/images/png_base64.txt'))) + expect(medium.file.blob.checksum).not_to eq(original_checksum) + expect(medium.file.blob.checksum).to eq(Digest::MD5.file(Rails.root.join('spec/factories/images/atl.png')).base64digest) + end + + it 'updates title and caption of video' do + medium = create(:medium, video: 'F9ULbmCvmxY', base_sixty_four: nil, video_provider: 'youtube') + original_checksum = medium.file.blob.checksum + expect(medium.title).to include('Goodie') + expect(medium.caption).to include('Goodie') + medium.update(title: 'Outkast') + medium.update(caption: 'GOATs') + expect(medium.title).not_to include('Goodie') + expect(medium.caption).not_to include('Goodie') + expect(medium.title).to include('Outkast') + expect(medium.caption).to include('GOATs') + # medium.update(base_sixty_four: File.read(Rails.root.join('spec/factories/images/png_base64.txt'))) + # expect(medium.file.blob.checksum).not_to eq(original_checksum) + # expect(medium.file.blob.checksum).to eq(Digest::MD5.file(Rails.root.join('spec/factories/images/atl.png')).base64digest) + end + + it 'skips video_props when provider in nil' do + medium = create(:medium, video: 'ACod3', base_sixty_four: nil) + expect(medium.file.attached?).to be false + end + end + + + context 'createing images' do + it 'sets widths for variants' do + medium = create( + :medium, + filename: Faker::File.file_name(dir: '', ext: 'jpg', directory_separator: ''), + base_sixty_four: File.read(Rails.root.join('spec/factories/images/atl_base64.txt')), + video: nil + ) + + medium.save + expect(medium.lqip_width).not_to be nil + end + + it 'saves a gif' do + medium = create( + :medium, + filename: Faker::File.file_name(dir: '', ext: 'gif', directory_separator: ''), + base_sixty_four: File.read(Rails.root.join('spec/factories/images/gif_base64.txt')), + video: nil + ) + + expect(medium.file.blob.checksum).to eq('4fqkSXu+qjQuQWCms8xBBQ==') + end + end end diff --git a/spec/models/role_spec.rb b/spec/models/role_spec.rb deleted file mode 100644 index 41d40605..00000000 --- a/spec/models/role_spec.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'rails_helper' - -RSpec.describe Role, type: :model do - pending "add some examples to (or delete) #{__FILE__}" -end diff --git a/spec/models/slug_spec.rb b/spec/models/slug_spec.rb index f7511438..95b75790 100644 --- a/spec/models/slug_spec.rb +++ b/spec/models/slug_spec.rb @@ -1,5 +1,5 @@ require 'rails_helper' RSpec.describe Slug, type: :model do - pending "add some examples to (or delete) #{__FILE__}" + it { should belong_to(:tour) } end diff --git a/spec/models/stop_slug_spec.rb b/spec/models/stop_slug_spec.rb index da4362c1..475ecc26 100644 --- a/spec/models/stop_slug_spec.rb +++ b/spec/models/stop_slug_spec.rb @@ -1,5 +1,5 @@ require 'rails_helper' RSpec.describe StopSlug, type: :model do - pending "add some examples to (or delete) #{__FILE__}" + it { should belong_to(:stop) } end diff --git a/spec/models/stop_spec.rb b/spec/models/stop_spec.rb index 62a0a016..fc93751f 100644 --- a/spec/models/stop_spec.rb +++ b/spec/models/stop_spec.rb @@ -8,4 +8,17 @@ # it { should validate_presence_of(:title) } it { should have_many(:stop_media) } it { should have_many(:media) } + + it 'has specified splash' do + stop = create(:stop, medium: create(:medium)) + expect(stop.splash).not_to be nil + end + + it 'has uses the first medium for splash' do + stop = create(:stop) + create_list(:medium, 3) + Medium.all.each { |medium| stop.media << medium } + expect(stop.splash).not_to be nil + expect(stop.splash[:title]).to eq(StopMedium.find_by(position: 1).medium.title) + end end diff --git a/spec/models/stop_tag_spec.rb b/spec/models/stop_tag_spec.rb deleted file mode 100644 index 19751ec0..00000000 --- a/spec/models/stop_tag_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe StopTag, type: :model do - it { should validate_presence_of(:title) } -end diff --git a/spec/models/tour_author_spec.rb b/spec/models/tour_author_spec.rb index 6ce24081..379098d3 100644 --- a/spec/models/tour_author_spec.rb +++ b/spec/models/tour_author_spec.rb @@ -1,5 +1,6 @@ require 'rails_helper' RSpec.describe TourAuthor, type: :model do - pending "add some examples to (or delete) #{__FILE__}" + it { should belong_to(:tour) } + it { should belong_to(:user) } end diff --git a/spec/models/tour_collection_spec.rb b/spec/models/tour_collection_spec.rb deleted file mode 100644 index 7edfa7d6..00000000 --- a/spec/models/tour_collection_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe TourCollection, type: :model do - pending "add some examples to (or delete) #{__FILE__}" -end diff --git a/spec/models/tour_set_spec.rb b/spec/models/tour_set_spec.rb index 873dbf19..e299b5a8 100644 --- a/spec/models/tour_set_spec.rb +++ b/spec/models/tour_set_spec.rb @@ -4,4 +4,32 @@ RSpec.describe TourSet, type: :model do it { should validate_presence_of(:name) } + + it 'creates four travel modes' do + tour_set = create(:tour_set) + Apartment::Tenant.switch! tour_set.subdir + expect(Mode.count).to eq(4) + end + + it 'attaches logo' do + tour_set = create(:tour_set) + expect(tour_set.logo.attached?).to be false + tour_set.update( + logo_title: Faker::File.file_name(dir: '', ext: 'png', directory_separator: ''), + base_sixty_four: File.read(Rails.root.join('spec/factories/images/png_base64.txt')) + ) + expect(tour_set.logo.attached?).to be true + end + + it 'removes logo' do + tour_set = create( + :tour_set, + logo_title: Faker::File.file_name(dir: '', ext: 'png', directory_separator: ''), + base_sixty_four: File.read(Rails.root.join('spec/factories/images/png_base64.txt')) + ) + tour_set.save + expect(tour_set.logo.attached?).to be true + tour_set.update(base_sixty_four: nil) + expect(tour_set.logo.attached?).to be false + end end diff --git a/spec/models/tour_set_user_spec.rb b/spec/models/tour_set_user_spec.rb deleted file mode 100644 index 8ea7360b..00000000 --- a/spec/models/tour_set_user_spec.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'rails_helper' - -RSpec.describe TourSetAdmin, type: :model do - pending "add some examples to (or delete) #{__FILE__}" -end diff --git a/spec/models/tour_spec.rb b/spec/models/tour_spec.rb index df3eea38..590687d0 100644 --- a/spec/models/tour_spec.rb +++ b/spec/models/tour_spec.rb @@ -9,4 +9,154 @@ it { expect(subject).to have_many(:tour_stops) } it { expect(Tour.reflect_on_association(:theme).macro).to eq(:belongs_to) } it { expect(Tour.reflect_on_association(:mode).macro).to eq(:belongs_to) } + + it 'gets a duration' do + tour = create(:tour, mode: Mode.find_by(title: 'BICYCLING'), stops: create_list(:stop, 5), published: false) + tour.update(published: true) + tour.save + expect(tour.duration).to eq(7336) + end + + it 'gets no duration when unpublished' do + tour = create(:tour, mode: Mode.find_by(title: 'TRANSIT'), stops: create_list(:stop, 5), published: false) + tour.update(published: false) + tour.save + expect(tour.duration).to be nil + end + + it 'gets duration when tour is updated to published' do + tour = create(:tour, mode: Mode.find_by(title: 'BICYCLING'), stops: create_list(:stop, 5), published: false) + tour.update(published: false) + expect(tour.duration).to be nil + tour.update(published: true) + expect(tour.saved_change_to_attribute?(:published)).to be true + expect(tour.duration).to eq(7336) + end + + it 'updates duration when mode changes' do + tour = create(:tour, mode: Mode.find_by(title: 'BICYCLING'), stops: create_list(:stop, 5), published: false) + tour.update(published: true) + tour.save + expect(tour.duration).to eq(7336) + tour.mode = Mode.find_by(title: 'TRANSIT') + expect(tour.will_save_change_to_mode_id?).to be true + tour.save + expect(tour.duration).to eq(6336) + expect(tour.saved_change_to_attribute?(:duration)).to be true + expect(tour.saved_change_to_attribute?(:saved_stop_order)).to be false + end + + it 'updates duration when stop order chages' do + tour = create(:tour, mode: Mode.find_by(title: 'BICYCLING'), published: false) + 5.times { |i| create(:tour_stop, tour: tour, stop: create(:stop), position: i + 1) } + tour.update(published: true) + tour.save + expect(tour.duration).to eq(7336) + # Trick the network stub to fetch different distance matrix but doesn't presist a + # change to the tour's mode. + tour.mode.title = 'TRANSIT' + tour.tour_stops.order(:position).last.update(position: 0) + tour.validate + # Make sure duration isn't being updated because we changed the mode or published status. + expect(tour.will_save_change_to_mode_id?).to be false + expect(tour.will_save_change_to_published?).to be false + expect(tour.will_save_change_to_saved_stop_order?).to be true + tour.save + expect(tour.duration).to eq(6336) + expect(tour.saved_change_to_attribute?(:duration)).to be true + end + + it 'gets no duration whin invalid request is made to Google' do + tour = create(:tour, mode: Mode.find_by(title: 'DRIVING'), stops: create_list(:stop, 5), published: false) + tour.update(published: true) + tour.save + expect(tour.duration).to be nil + end + + it 'gets no duration whin response has ZERO_RESULTS' do + tour = create(:tour, mode: Mode.find_by(title: 'WALKING'), stops: create_list(:stop, 4), published: false) + tour.update(published: true) + tour.save + expect(tour.duration).to be nil + end + + it 'does not update the duration when other attributes are updaeted' do + tour = create(:tour, mode: Mode.find_by(title: 'BICYCLING'), stops: create_list(:stop, 5), published: false) + tour.update(published: true) + tour.save + expect(tour.duration).to eq(7336) + # Trick the network stub to fetch different distance matrix but doesn't presist a + # change to the tour's mode. In this case, it should NOT fetch. This is just to + # test that it does not actually make teh request when we don't want it to. + tour.mode.title = 'TRANSIT' + tour.update( + title: Faker::Music::Prince.band, + description: Faker::Music::Prince.lyric + ) + expect(tour.saved_change_to_attribute?(:title)).to be true + expect(tour.saved_change_to_attribute?(:description)).to be true + expect(tour.saved_change_to_attribute?(:duration)).to be false + expect(tour.saved_change_to_attribute?(:published)).to be false + expect(tour.saved_change_to_attribute?(:saved_stop_order)).to be false + expect(tour.duration).to eq(7336) + end + + it 'when restricted to overlay bounds, tour bounds mirror overlay' do + tour = create(:tour, mode: Mode.find_by(title: 'BICYCLING'), stops: create_list(:stop, 5), published: false) + mo = create(:map_overlay, tour: tour) + mo.update( + south: '33.73324867399921', + north: '33.81498938289962', + east: '-84.25453244903566', + west: '-84.37135369046021' + ) + tour.update(restrict_bounds_to_overlay: true) + expect(tour.bounds[:south]).to eq(33.723031085386665) + end + + it 'has no bounds when no stops' do + tour = create(:tour, mode: Mode.find_by(title: 'BICYCLING'), published: false) + expect(tour.bounds).to be nil + end + + it 'does not restrict bounds to overlay when no overlay' do + tour = create(:tour, mode: Mode.find_by(title: 'BICYCLING'), stops: create_list(:stop, 5), restrict_bounds_to_overlay: true) + expect(tour.restrict_bounds_to_overlay).to be false + end + + it 'sets restrict_bounds to false when restricted to overlay bounds' do + tour = create(:tour, mode: Mode.find_by(title: 'BICYCLING'), stops: create_list(:stop, 5), restrict_bounds_to_overlay: true) + mo = create(:map_overlay, tour: tour) + expect(tour.restrict_bounds).to be true + tour.update(restrict_bounds_to_overlay: true) + expect(tour.restrict_bounds).to be false + expect(tour.restrict_bounds_to_overlay).to be true + end + + it 'sets restrict_to_overlay_bounds when updated to restrict_bounds' do + tour = create(:tour, mode: Mode.find_by(title: 'BICYCLING'), stops: create_list(:stop, 5), restrict_bounds_to_overlay: true) + mo = create(:map_overlay, tour: tour) + expect(tour.restrict_bounds).to be true + tour.update(restrict_bounds_to_overlay: true) + expect(tour.restrict_bounds).to be false + expect(tour.restrict_bounds_to_overlay).to be true + tour.update(restrict_bounds: true) + expect(tour.restrict_bounds).to be true + expect(tour.restrict_bounds_to_overlay).to be false + end + + it 'allows both restrictions to be false' do + tour = create(:tour, mode: Mode.find_by(title: 'BICYCLING'), stops: create_list(:stop, 5)) + mo = create(:map_overlay, tour: tour) + expect(tour.restrict_bounds).to be true + tour.update(restrict_bounds: false) + expect(tour.restrict_bounds).to be false + expect(tour.restrict_bounds_to_overlay).to be false + end + + it 'will not allow restriction to overlay if no overlay' do + tour = create(:tour, mode: Mode.find_by(title: 'BICYCLING'), stops: create_list(:stop, 5)) + tour.update(restrict_bounds_to_overlay: true) + expect(tour.restrict_bounds_to_overlay).to be false + end end diff --git a/spec/models/tour_stop_spec.rb b/spec/models/tour_stop_spec.rb index 7d129373..3f7bab93 100644 --- a/spec/models/tour_stop_spec.rb +++ b/spec/models/tour_stop_spec.rb @@ -5,5 +5,4 @@ RSpec.describe TourStop, type: :model do it { should belong_to(:tour) } it { should belong_to(:stop) } - it "is not valid without a title" end diff --git a/spec/models/tour_tag_spec.rb b/spec/models/tour_tag_spec.rb deleted file mode 100644 index 9e26929f..00000000 --- a/spec/models/tour_tag_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe TourTag, type: :model do - it { should validate_presence_of(:title) } -end diff --git a/spec/models/user_spec-fix.rb b/spec/models/user_spec-fix.rb deleted file mode 100644 index ebcc26cf..00000000 --- a/spec/models/user_spec-fix.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe User, type: :model do - it { should have_one(:login) } - it { expect(User.reflect_on_association(:login).macro).to eq(:has_one) } - - context 'creates login' do - it 'creates login' do - pw = Faker::Internet.password(min_length: 8) - u = User.create!(displayname: Faker::Movies::HitchhikersGuideToTheGalaxy.character) - Login.create!(identification: 'foo@bar.com', password: pw, password_confirmation: pw, user: u) - # RailsApiAuth uses `has_secure_password` The `authenticate` method returns - # the `Login` object. This just checks that the password authenticates - expect(u.login.authenticate(pw).user).to eq(u) - end - end -end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb new file mode 100644 index 00000000..0f1a4be1 --- /dev/null +++ b/spec/models/user_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe User, type: :model do + # it { should have_one(:login) } + # it { expect(User.reflect_on_association(:login).macro).to eq(:has_one) } + + context 'tour author across tenants' do + it 'lists all tours across tenants' do + # pw = Faker::Internet.password(min_length: 8) + # u = User.create!(displayname: Faker::Movies::HitchhikersGuideToTheGalaxy.character) + # Login.create!(identification: 'foo@bar.com', password: pw, password_confirmation: pw, user: u) + # # RailsApiAuth uses `has_secure_password` The `authenticate` method returns + # # the `Login` object. This just checks that the password authenticates + # expect(u.login.authenticate(pw).user).to eq(u) + TourSet.all.each { |tour_set| tour_set.delete } + user = create(:user) + create_list(:tour_set, 4) + TourSet.all.each do |tour_set| + Apartment::Tenant.switch! tour_set.subdir + user.tours << create_list(:tour, 2) + end + expect(user.all_tours.count).to eq(8) + end + end + + context 'has login' do + it 'has no provider' do + user = create(:user) + expect(user.provider).to be nil + end + + it 'has no provider' do + user = create(:user) + login = create(:login, user_id: user.id) + expect(user.provider).to eq(login.provider) + end + end + + context 'has default' do + it 'terms accepted defaults to false' do + user = create(:user) + expect(user.terms_accepted).to be(false) + end + end +end diff --git a/spec/models/v3/flat_page_spec.rb b/spec/models/v3/flat_page_spec.rb deleted file mode 100644 index 87dcab8c..00000000 --- a/spec/models/v3/flat_page_spec.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'rails_helper' - -RSpec.describe V3::FlatPage, type: :model do - pending "add some examples to (or delete) #{__FILE__}" -end diff --git a/spec/models/v3/tour_medium_spec.rb b/spec/models/v3/tour_medium_spec.rb deleted file mode 100644 index c55075d3..00000000 --- a/spec/models/v3/tour_medium_spec.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'rails_helper' - -RSpec.describe V3::TourMedium, type: :model do - pending "add some examples to (or delete) #{__FILE__}" -end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 47734592..8484bcbb 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -3,7 +3,7 @@ # This file is copied to spec/ when you run 'rails generate rspec:install' require 'spec_helper' ENV['RAILS_ENV'] ||= 'test' -require File.expand_path('../../config/environment', __FILE__) +require(File.expand_path('../config/environment', __dir__)) # Prevent database truncation if the environment is production abort('The Rails environment is running in production mode!') if Rails.env.production? require 'rspec/rails' @@ -43,9 +43,14 @@ # If you're not using ActiveRecord, or you'd prefer not to run each of your # examples within a transaction, remove the following line or assign false # instead of true. - config.use_transactional_fixtures = true + if ENV['DB_ADAPTER'] == 'postgresql' + # config.use_transactional_fixtures = true + end config.include RequestSpecHelper, type: :request + config.include(RequestSpecHelper, type: :controller) + config.include(SignedCookieHelper, type: :request) + config.include(SignedCookieHelper, type: :controller) config.include FactoryBot::Syntax::Methods # start by truncating all the tables but then use the faster transaction strategy the rest of the time. config.before(:suite) do @@ -66,20 +71,31 @@ # start the transaction strategy as examples are run config.around(:each) do |example| - # DatabaseCleaner.cleaning do - example.run - # end + example.run end config.before(:each) do + MiniMagick.configure do |config| + config.validate_on_create = false + end # Start transaction for this test # DatabaseCleaner.start # Switch into the default tenant Apartment::Tenant.switch! TourSet.find(TourSet.pluck(:id).sample).subdir + + # Set the host for ActiveStorage urls + ActiveStorage::Current.host = 'http://test.host' + # Switch to the below version for Rails 7 + # ActiveStorage::Current.url_options = { host: 'http://test.host' } # host! 'atlanta.lvh.me' # load Rails.root + 'db/seeds.rb' - stub_request(:any, 'https://placehold.it/300x300.png') - .to_return(body: File.open(Rails.root + 'spec/factories/images/300x300.png'), status: 200) + + # Stub a network requests + stub_request(:get, 'https://placehold.it/300x300.png_1000x1000') + .to_return( + body: File.open(Rails.root + 'spec/factories/images/0.jpg'), + status: 200 + ) stub_request(:get, 'https://vimeo.com/api/oembed.json?url=https://vimeo.com/310645255') .to_return( @@ -97,7 +113,7 @@ ) .to_return( status: 200, - body: '{ "title": "CycloramaBattleSites.org Stop 2", "thumbnail_url": "https://placehold.it/300x300.png" }', + body: '{ "title": "CycloramaBattleSites.org Stop 2", "thumbnail_url": "https://placehold.it/300x300.png", "thumbnail_width": 100, "thumbnail_height": 100 }', headers: { 'content-type': 'application/json' } ) @@ -117,6 +133,12 @@ status: 200 ) + stub_request(:get, /http:\/\/test\.host\/rails\/active_storage\/.*/) + .to_return( + body: File.open(Rails.root + 'spec/factories/images/atl.png'), + status: 200 + ) + stub_request( :get, 'https://www.googleapis.com/youtube/v3/videos?id=F9ULbmCvmxY&key=AIzaSyAafrj3VvNLJNXeW5-NNCVwY5cdB06p1_s&part=snippet' @@ -136,9 +158,47 @@ stub_request(:get, 'https://vimeo.com/https://youtu.be/F9ULbmCvmxY') .to_return(status: 404, body: '', headers: {}) - stub_request(:get, 'https://vimeo.com/https://www.youtube.com/watch?v=F9ULbmCvmxY') - .to_return(status: 404, body: '', headers: {}) + stub_request(:get, 'https://www.googleapis.com/youtube/v3/videos?id=CvmxYF9ULbm&key=AIzaSyAafrj3VvNLJNXeW5-NNCVwY5cdB06p1_s&part=snippet') + .to_return( + status: 200, + body: '{"kind": "youtube#videoListResponse", "etag": "YIUPVpqNjppyCWOZfL-19bLb7uk", "items": [ ], "pageInfo": { "totalResults": 0, "resultsPerPage": 0 } }', + headers: { 'content-type': 'application/json' } + ) + stub_request(:get, 'https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/431162745&color=%23ff5500&auto_play=false&hide_related=true&show_comments=false&show_user=false&show_reposts=false&show_teaser=false&visual=true&sharing=false') + .to_return( + status: 200, + body: '', + headers: {} + ) + + stub_request(:get, 'https://w.soundcloud.com/player/?auto_play=false&color=%23ff5500&hide_related=true&sharing=false&show_comments=false&show_reposts=false&show_teaser=false&show_user=false&url=https://api.soundcloud.com/tracks/457871163&visual=true') + .to_return( + status: 200, + body: '
', + headers: {} + ) + + stub_request(:get, /https:\/\/i1\.sndcdn.com\/artworks-.*\.jpg/) + .to_return( + body: File.open(Rails.root + 'spec/factories/images/0.jpg'), + status: 200, + headers: {} + ) + + stub_request(:get, /http:\/\/127\.0\.0\.1:.*\/json\/version/).to_return(body: '{}', status: 200) + + stub_request(:get, /http.*:\/\/maps\.googleapis\.com\/maps\/api\/.*BICYCLING.*/) + .to_return(body: File.read(Rails.root + 'spec/factories/distance_matrix.json'), status: 200, headers: { 'Content-Type': 'application/json' }) + + stub_request(:get, /http.*:\/\/maps\.googleapis\.com\/maps\/api\/.*TRANSIT.*/) + .to_return(body: File.read(Rails.root + 'spec/factories/distance_matrix2.json'), status: 200, headers: { 'Content-Type': 'application/json' }) + + stub_request(:get, /http.*:\/\/maps\.googleapis\.com\/maps\/api\/.*WALKING.*/) + .to_return(body: File.read(Rails.root + 'spec/factories/distance_matrix_zero.json'), status: 200, headers: { 'Content-Type': 'application/json' }) + + stub_request(:get, /http.*:\/\/maps\.googleapis\.com\/maps\/api\/.*DRIVING.*/) + .to_return(body: '{"status": "INVALID_REQUEST"}', status: 200, headers: { 'Content-Type': 'application/json' }) end config.after(:each) do @@ -178,6 +238,17 @@ end config.after(:suite) do - TourSet.all.each { |ts| ts.destroy } + # TourSet.all.each { |ts| ts.destroy } + end + + # Class to mock IPinfo + class MockIpinfo + def longitude + Faker::Address.longitude + end + + def latitude + Faker::Address.latitude + end end end diff --git a/spec/requests/tours_request_spec.rb b/spec/requests/tours_request_spec.rb new file mode 100755 index 00000000..a0b877c0 --- /dev/null +++ b/spec/requests/tours_request_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'V3::Tours', type: :request do + describe 'GET /:tenant/tours' do + before { + get "/#{Apartment::Tenant.current}/tours", headers: { 'HTTP_USER_AGENT': 'bot' } + } + + it 'returns only published tours' do + expect(json.size).to eq(Tour.published.count) + end + + it 'returns status code 200' do + expect(response).to have_http_status(200) + end + end +end diff --git a/spec/requests/v3/flat_pages_spec.rb b/spec/requests/v3/flat_pages_spec.rb deleted file mode 100644 index 6a32be05..00000000 --- a/spec/requests/v3/flat_pages_spec.rb +++ /dev/null @@ -1,117 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' -require 'ecds_rails_auth_engine' - -RSpec.describe 'V3::FlatPages', type: :request do - let!(:theme) { create(:theme) } - let!(:tours) { create_list(:tour_with_flat_pages, 1, theme: theme) } - let!(:flat_page) { tours.first.flat_pages.first } - let(:tour_id) { tours.first.id } - - context 'create tour with flat pages' do - before { get "/#{Apartment::Tenant.current}/flat-pages" } - - it 'associates flat_page with tour' do - expect(response).to have_http_status(200) - expect(json.size).to eq(3) - end - end - - context 'flat page included in tours payload' do - before { - tour = create(:tour_with_flat_pages, theme: theme) - tour.published = true - tour.save - get "/#{Apartment::Tenant.current}/tours?slug=#{tour.slug}" - } - - it 'creates tour with existing flat page' do - expect(included.select { |d| d['type'] == 'tour_flat_pages' }.length).to eq(3) - end - end - - context 'get specific flat page by id' do - before { get "/#{Apartment::Tenant.current}/flat-pages/#{FlatPage.first.id}" } - - it 'returns requested flat page' do - expect(response).to have_http_status(200) - expect(attributes['title']).to eq(FlatPage.first.title) - end - end - - # valid payload - let(:valid_attributes) do - factory_to_json_api(FactoryBot.build(:flat_page)) - end - - describe 'POST /tenant/flat-pages' do - - context 'create page not authenticated' do - before { post "/#{Apartment::Tenant.current}/flat-pages", params: valid_attributes } - it 'returns status code 401' do - expect(response).to have_http_status(401) - end - end - - context 'when created by tour set admin' do - before { User.first.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) } - before { post "/#{Apartment::Tenant.current}/flat-pages", params: valid_attributes, headers: { Authorization: "Bearer #{User.first.login.oauth2_token}" } } - it 'creates a tour' do - expect(response).to have_http_status(201) - end - end - - context 'create without valid params' do - before { post "/#{Apartment::Tenant.current}/flat-pages", params: {foo: 'bar'}, headers: { Authorization: "Bearer #{User.first.login.oauth2_token}" } } - it 'returns unprocessable entity' do - expect(response).to have_http_status(422) - end - end - end - - describe 'PUT /tenant/flat-pages/' do - context 'update page not authenticated' do - before { put "/#{Apartment::Tenant.current}/flat-pages/#{FlatPage.first.id}", params: valid_attributes} - it 'returns status code 401' do - expect(response).to have_http_status(401) - end - end - - context 'when updated by tour set admin' do - before { User.first.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) } - before { put "/#{Apartment::Tenant.current}/flat-pages/#{FlatPage.first.id}", params: valid_attributes, headers: { Authorization: "Bearer #{User.first.login.oauth2_token}" } } - it 'creates a tour' do - expect(response).to have_http_status(200) - end - end - - context 'update without valid params' do - before { - invalid_attributes = {data: {type: 'flat_pages', attributes: {title: nil}}} - put "/#{Apartment::Tenant.current}/flat-pages/#{FlatPage.last.id}", params: invalid_attributes, headers: { Authorization: "Bearer #{User.first.login.oauth2_token}" } - } - it 'returns unprocessable entity' do - expect(response).to have_http_status(422) - end - end - - end - - describe 'DELETE /tenant/flat-pages/' do - context 'delete page not authenticated' do - before { delete "/#{Apartment::Tenant.current}/flat-pages/#{FlatPage.first.id}", params: valid_attributes} - it 'returns status code 401' do - expect(response).to have_http_status(401) - end - end - - context 'when deletes by tour set admin' do - before { User.first.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) } - before { delete "/#{Apartment::Tenant.current}/flat-pages/#{FlatPage.first.id}", params: valid_attributes, headers: { Authorization: "Bearer #{User.first.login.oauth2_token}" } } - it 'creates a tour' do - expect(response).to have_http_status(204) - end - end - end -end diff --git a/spec/routing/map_icons_routing_spec.rb.fix b/spec/routing/map_icons_routing_spec.rb.fix new file mode 100644 index 00000000..7ed19400 --- /dev/null +++ b/spec/routing/map_icons_routing_spec.rb.fix @@ -0,0 +1,30 @@ +require "rails_helper" + +RSpec.describe MapIconsController, type: :routing do + describe "routing" do + it "routes to #index" do + expect(get: "/map_icons").to route_to("map_icons#index") + end + + it "routes to #show" do + expect(get: "/map_icons/1").to route_to("map_icons#show", id: "1") + end + + + it "routes to #create" do + expect(post: "/map_icons").to route_to("map_icons#create") + end + + it "routes to #update via PUT" do + expect(put: "/map_icons/1").to route_to("map_icons#update", id: "1") + end + + it "routes to #update via PATCH" do + expect(patch: "/map_icons/1").to route_to("map_icons#update", id: "1") + end + + it "routes to #destroy" do + expect(delete: "/map_icons/1").to route_to("map_icons#destroy", id: "1") + end + end +end diff --git a/spec/routing/tour_authors_routing_spec.rb.fix b/spec/routing/tour_authors_routing_spec.rb.fix new file mode 100644 index 00000000..2945dc40 --- /dev/null +++ b/spec/routing/tour_authors_routing_spec.rb.fix @@ -0,0 +1,30 @@ +require "rails_helper" + +RSpec.describe TourAuthorsController, type: :routing do + describe "routing" do + it "routes to #index" do + expect(get: "/tour_authors").to route_to("tour_authors#index") + end + + it "routes to #show" do + expect(get: "/tour_authors/1").to route_to("tour_authors#show", id: "1") + end + + + it "routes to #create" do + expect(post: "/tour_authors").to route_to("tour_authors#create") + end + + it "routes to #update via PUT" do + expect(put: "/tour_authors/1").to route_to("tour_authors#update", id: "1") + end + + it "routes to #update via PATCH" do + expect(patch: "/tour_authors/1").to route_to("tour_authors#update", id: "1") + end + + it "routes to #destroy" do + expect(delete: "/tour_authors/1").to route_to("tour_authors#destroy", id: "1") + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 1fe4732a..77c71181 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,10 +1,44 @@ # frozen_string_literal: true +# if ENV['COV'] == 'simple' +# require 'simplecov' +# else +# require 'coveralls' +# Coveralls.wear! +# end + +# require 'simplecov' +# SimpleCov.start + require 'coveralls' -Coveralls.wear! +require 'simplecov' +require 'simplecov-lcov' + +SimpleCov::Formatter::LcovFormatter.config.report_with_single_file = true + +SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new( + [ + SimpleCov::Formatter::HTMLFormatter, + Coveralls::SimpleCov::Formatter, + SimpleCov::Formatter::LcovFormatter + ] +) + +# SimpleCov.start 'rails' +SimpleCov.start 'rails' do + add_filter [ + '/lib/snippets.rb', + '/app/channels/', + '/app/mailers/', + '/app/jobs/', + '/app/uploaders/' +] +end require 'webmock/rspec' WebMock.disable_net_connect!(allow_localhost: true) +WebMock.disable_net_connect!(allow: '45.33.24.119') +WebMock.disable_net_connect!(allow: 'http://test.host') # This file was generated by the `rails generate rspec:install` command. Conventionally, all # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. diff --git a/spec/string_spec.rb b/spec/string_spec.rb new file mode 100644 index 00000000..c87d4411 --- /dev/null +++ b/spec/string_spec.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Using custom parameterize_intl method' do + it 'uses custom parameterize_intl method' do + expect('My Awesome Tour'.parameterize_intl).to eq('my-awesome-tour') + expect('Csodálatos túrám'.parameterize_intl).to eq('csodalatos-turam') + expect('הסיור המדהים שלי'.parameterize_intl).to eq('הסיור-המדהים-שלי') + expect('我的精 彩之旅!!'.parameterize_intl).to eq('我的精-彩之旅') + expect('Мій чудовий тур'.parameterize_intl).to eq('мій-чудовий-тур') + expect('جولتي الرائعة'.parameterize_intl).to eq('جولتي-الرائعة') + expect('我的精 彩之旅!! (mix)'.parameterize_intl).to eq('我的精-彩之旅-mix') + expect('💩 🤯 🤷🏼'.parameterize_intl).to eq('💩-🤯-🤷🏼') + end +end diff --git a/spec/support/cookies.rb b/spec/support/cookies.rb new file mode 100644 index 00000000..b611978a --- /dev/null +++ b/spec/support/cookies.rb @@ -0,0 +1,6 @@ +# spec/support/cookies.rb +class Rack::Test::CookieJar + def encrypted; self; end + def signed; self; end + def permanent; self; end # I needed this, too +end \ No newline at end of file diff --git a/spec/support/database_cleaner.rb b/spec/support/database_cleaner.rb index 4df8a28c..69f033be 100644 --- a/spec/support/database_cleaner.rb +++ b/spec/support/database_cleaner.rb @@ -7,7 +7,7 @@ DatabaseCleaner.strategy = :transaction end - config.before(:each, :js => true) do + config.before(:each, js: true) do DatabaseCleaner.strategy = :truncation end @@ -16,6 +16,10 @@ end config.after(:each) do - DatabaseCleaner.clean + begin + DatabaseCleaner.clean + rescue NoMethodError + # IDK + end end -end \ No newline at end of file +end diff --git a/spec/support/request_spec_helper.rb b/spec/support/request_spec_helper.rb index 71d8377d..5f587f24 100644 --- a/spec/support/request_spec_helper.rb +++ b/spec/support/request_spec_helper.rb @@ -4,7 +4,11 @@ module RequestSpecHelper # Parse JSON response to ruby hash def json - JSON.parse(response.body)['data'] + JSON.parse(response.body).with_indifferent_access[:data] + end + + def errors + JSON.parse(response.body).with_indifferent_access[:errors].map { |e| e[:detail] } end def response_id @@ -12,6 +16,9 @@ def response_id end def attributes + if json.is_a?(Array) + return json.map { |record| record[:attributes] } + end json['attributes'] end @@ -20,7 +27,7 @@ def relationships end def included - JSON.parse(response.body)['included'] + JSON.parse(response.body).with_indifferent_access[:included] end def hash_to_json_api(model, attributes) diff --git a/spec/support/signed_cookie.rb b/spec/support/signed_cookie.rb new file mode 100755 index 00000000..9a86c888 --- /dev/null +++ b/spec/support/signed_cookie.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +# +# Helper for making authenticated requests for controller specs. +# +module SignedCookieHelper + def signed_cookie(user) + login = EcdsRailsAuthEngine::Login.create!(who: user.email) + login.user_id = user.id + login.save! + create(:token, login: login, token: TokenService.create(login)) + cookies.signed[:auth] = { + value: login.tokens.first.token, + httponly: true, + same_site: :none, + secure: 'Secure' + } + end +end diff --git a/temp_gemfile b/temp_gemfile new file mode 100644 index 00000000..e69de29b