diff --git a/.rubocop.yml b/.rubocop.yml index 615b2430..79a8627b 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,148 +1,7 @@ -AllCops: - TargetRubyVersion: 2.5 - # RuboCop has a bunch of cops enabled by default. This setting tells RuboCop - # to ignore them, so only the ones explicitly set in this file are enabled. - DisabledByDefault: true - Exclude: - - '**/templates/**/*' - - '**/vendor/**/*' - - 'actionpack/lib/action_dispatch/journey/parser.rb' - -# Prefer &&/|| over and/or. -Style/AndOr: - Enabled: true - -# Do not use braces for hash literals when they are the last argument of a -# method call. -# Style/BracesAroundHashParameters: -# Enabled: true -# EnforcedStyle: context_dependent - -# Align `when` with `case`. -Layout/CaseIndentation: - Enabled: true - -# Align comments with method definitions. -Layout/CommentIndentation: - Enabled: true - -Layout/EmptyLineAfterMagicComment: - Enabled: true - -# In a regular class definition, no empty lines around the body. -Layout/EmptyLinesAroundClassBody: - Enabled: true - -# In a regular method definition, no empty lines around the body. -Layout/EmptyLinesAroundMethodBody: - Enabled: true - -# In a regular module definition, no empty lines around the body. -Layout/EmptyLinesAroundModuleBody: - Enabled: true - -Layout/FirstParameterIndentation: - Enabled: true - -# Use Ruby >= 1.9 syntax for hashes. Prefer { a: :b } over { :a => :b }. -Style/HashSyntax: - Enabled: true - -# Method definitions after `private` or `protected` isolated calls need one -# extra level of indentation. -Layout/IndentationConsistency: - Enabled: true - EnforcedStyle: indented_internal_methods - -# Two spaces, no tabs (for indentation). -Layout/IndentationWidth: - Enabled: true - -Layout/SpaceAfterColon: - Enabled: true - -Layout/SpaceAfterComma: - Enabled: true - -Layout/SpaceAroundEqualsInParameterDefault: - Enabled: true - -Layout/SpaceAroundKeyword: - Enabled: true - -Layout/SpaceAroundOperators: - Enabled: true - -Layout/SpaceBeforeFirstArg: - Enabled: true - -Style/DefWithParentheses: - Enabled: true - -# Defining a method with parameters needs parentheses. -Style/MethodDefParentheses: - Enabled: true - -Style/FrozenStringLiteralComment: - Enabled: true - EnforcedStyle: always - Exclude: - - 'actionview/test/**/*.builder' - - 'actionview/test/**/*.ruby' - - 'actionpack/test/**/*.builder' - - 'actionpack/test/**/*.ruby' - - 'activestorage/db/migrate/**/*.rb' - -# Use `foo {}` not `foo{}`. -Layout/SpaceBeforeBlockBraces: - Enabled: true - -# Use `foo { bar }` not `foo {bar}`. -Layout/SpaceInsideBlockBraces: - Enabled: true - -# Use `{ a: 1 }` not `{a:1}`. -Layout/SpaceInsideHashLiteralBraces: - Enabled: true - -Layout/SpaceInsideParens: - Enabled: true - -# Check quotes usage according to lint rule below. -Style/StringLiterals: - Enabled: true - EnforcedStyle: single_quotes - -# Detect hard tabs, no hard tabs. -Layout/IndentationStyle: - Enabled: true - -# Blank lines should not have any spaces. -Layout/TrailingEmptyLines: - Enabled: true - -# No trailing whitespace. -Layout/TrailingWhitespace: - Enabled: true - -# Use quotes for string literals when they are enough. -Style/RedundantPercentQ: - Enabled: true - -# Align `end` with the matching keyword or starting expression except for -# assignments, where it should be aligned with the LHS. -Layout/EndAlignment: - Enabled: true - EnforcedStyleAlignWith: variable - -# Use my_method(my_arg) not my_method( my_arg ) or my_method my_arg. -Lint/RequireParentheses: - Enabled: true - -Style/RedundantReturn: - Enabled: true - AllowMultipleReturnValues: true - -Style/Semicolon: - Enabled: true - AllowAsExpressionSeparator: true +# Omakase Ruby styling for Rails +inherit_gem: { rubocop-rails-omakase: rubocop.yml } +# Overwrite or add rules to create your own house style +# +# # Use `[a, [b, c]]` not `[ a, [ b, c ] ]` +# Layout/SpaceInsideArrayLiteralBrackets: +# Enabled: false diff --git a/.ruby-version b/.ruby-version index ef538c28..f9892605 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.1.2 +3.4.4 diff --git a/Dockerfile b/Dockerfile index a2610361..cff3cbe5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,19 +1,22 @@ -FROM ruby:3.1.2-slim +FROM ruby:3.4.4-slim + +ENV RAILS_ENV=production 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/* + build-essential \ + curl \ + git \ + postgresql-client \ + libpq-dev \ + libgdal-dev \ + gdal-bin \ + imagemagick \ + libmagickwand-dev \ + libvips \ + libyaml-dev \ + pkg-config \ + && rm -rf /var/lib/apt/lists/* WORKDIR /rails diff --git a/Gemfile b/Gemfile index d948983e..0837b204 100644 --- a/Gemfile +++ b/Gemfile @@ -1,61 +1,68 @@ # frozen_string_literal: true -source 'https://rubygems.org' +source "https://rubygems.org" git_source(:github) do |repo_name| - repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?('/') + 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', '~> 7.0.4' -gem 'rails', '~> 6.1.0' +gem "rails", "~> 8.0.0" + +gem "pg" + +# Omakase Ruby styling [https://github.com/rails/rubocop-rails-omakase/] +gem "rubocop-rails-omakase", require: false -gem 'rack', '>= 2.0.6' -gem 'pg' -gem 'mysql2' # Multitenancy for Rails and ActiveRecord -gem 'ros-apartment', require: 'apartment' +gem "ros-apartment", "~> 3.4.0", require: "apartment" # For JSONAPI responses -gem 'active_model_serializers', '~> 0.10.12' +gem "active_model_serializers", "~> 0.10.12" # For pagination -gem 'kaminari' +gem "kaminari" # gem 'pagy', '~> 5.10' # Use Puma as the app server -gem 'puma', '~> 4.3.0' +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 "redis", "~> 3.0" +gem "actionview", ">= 5.2.2.1" # Use ActiveModel has_secure_password # gem 'bcrypt', '~> 3.1.7' # gem 'ecds_rails_auth_engine', path: '../ecds_auth_engine' -gem 'ecds_rails_auth_engine', git: 'https://github.com/ecds/ecds_rails_auth_engine.git', branch: 'feature/fauxoauth' +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 'image_processing', '~> 1.2' -gem 'ferrum' -gem 'aws-sdk-s3', '~> 1' +gem "carrierwave", "~> 1.0" +gem "mini_magick" +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' +gem "rgeo" +gem "ipinfo-rails" +# Elasticsearch +gem "elasticsearch", "~> 7.17.1" +gem "searchkick" +gem "sidekiq", ">=7.2.2", "<8" +gem "faraday-httpclient", "~> 2.0" # Vidoe provider APIs -gem 'vimeo' -gem 'yt' -gem 'youtube_rails' +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' +gem "rack-cors" # TODO: should probably only require this for :test -gem 'faker' +gem "faker" group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console @@ -64,32 +71,25 @@ group :development, :test do end group :development do - gem 'listen' + 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', '~> 5.1.2' - # Use Capistrano for deployment - gem 'capistrano-rails' - gem 'capistrano-rbenv', '~> 2.0' - gem 'capistrano-passenger' + gem "spring" + gem "spring-watcher-listen", "~> 2.0.0" + gem "rspec-rails", "~> 6.1.0" end group :test do - gem 'factory_bot' - gem 'factory_bot_rails' - gem 'shoulda-matchers', '~> 4.5.1' #git: 'https://github.com/thoughtbot/shoulda-matchers.git', branch: 'rails-5' - gem 'database_cleaner' - gem 'webmock' - gem 'coveralls', require: false - gem 'simplecov', require: false - gem 'simplecov-lcov', require: false - gem 'term-ansicolor' + gem "factory_bot" + gem "factory_bot_rails" + gem "shoulda-matchers", "~> 4.5.1" # git: 'https://github.com/thoughtbot/shoulda-matchers.git', branch: 'rails-5' + gem "database_cleaner" + gem "webmock" + 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 +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.lock b/Gemfile.lock index 48a30662..0f2994f9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,209 +1,209 @@ GIT remote: https://github.com/ecds/ecds_rails_auth_engine.git - revision: fe56d7d214403689a0ab0ab79cbc9d343fdc63f6 + revision: ef48c2a42cca0650e149e67add752c7793a52f4b branch: feature/fauxoauth specs: ecds_rails_auth_engine (0.2.0) cancancan httparty - jwt + jwt (<= 2.2.2) rails GEM remote: https://rubygems.org/ specs: - IPinfo (1.0.1) - faraday (~> 1.0) + IPinfo (2.4.0) + faraday (~> 2.0) json (~> 2.1) lru_redux (~> 1.1) - actioncable (6.1.6.1) - actionpack (= 6.1.6.1) - activesupport (= 6.1.6.1) + actioncable (8.0.4) + actionpack (= 8.0.4) + activesupport (= 8.0.4) nio4r (~> 2.0) websocket-driver (>= 0.6.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 (6.1.6.1) - actionview (= 6.1.6.1) - activesupport (= 6.1.6.1) - rack (~> 2.0, >= 2.0.9) + zeitwerk (~> 2.6) + actionmailbox (8.0.4) + actionpack (= 8.0.4) + activejob (= 8.0.4) + activerecord (= 8.0.4) + activestorage (= 8.0.4) + activesupport (= 8.0.4) + mail (>= 2.8.0) + actionmailer (8.0.4) + actionpack (= 8.0.4) + actionview (= 8.0.4) + activejob (= 8.0.4) + activesupport (= 8.0.4) + mail (>= 2.8.0) + rails-dom-testing (~> 2.2) + actionpack (8.0.4) + actionview (= 8.0.4) + activesupport (= 8.0.4) + nokogiri (>= 1.8.5) + rack (>= 2.2.4) + rack-session (>= 1.0.1) rack-test (>= 0.6.3) - rails-dom-testing (~> 2.0) - 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) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + useragent (~> 0.16) + actiontext (8.0.4) + actionpack (= 8.0.4) + activerecord (= 8.0.4) + activestorage (= 8.0.4) + activesupport (= 8.0.4) + globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (6.1.6.1) - activesupport (= 6.1.6.1) + actionview (8.0.4) + activesupport (= 8.0.4) builder (~> 3.1) - erubi (~> 1.4) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.1, >= 1.2.0) - active_model_serializers (0.10.13) - actionpack (>= 4.1, < 7.1) - activemodel (>= 4.1, < 7.1) + erubi (~> 1.11) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + active_model_serializers (0.10.16) + actionpack (>= 4.1) + activemodel (>= 4.1) case_transform (>= 0.2) jsonapi-renderer (>= 0.1.1.beta1, < 0.3) - activejob (6.1.6.1) - activesupport (= 6.1.6.1) + activejob (8.0.4) + activesupport (= 8.0.4) globalid (>= 0.3.6) - 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) + activemodel (8.0.4) + activesupport (= 8.0.4) + activerecord (8.0.4) + activemodel (= 8.0.4) + activesupport (= 8.0.4) + timeout (>= 0.4.0) + activestorage (8.0.4) + actionpack (= 8.0.4) + activejob (= 8.0.4) + activerecord (= 8.0.4) + activesupport (= 8.0.4) marcel (~> 1.0) - mini_mime (>= 1.1.0) - activesupport (6.1.6.1) - concurrent-ruby (~> 1.0, >= 1.0.2) + activesupport (8.0.4) + base64 + benchmark (>= 0.3) + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb i18n (>= 1.6, < 2) + logger (>= 1.4.2) minitest (>= 5.1) - tzinfo (~> 2.0) - zeitwerk (~> 2.3) - addressable (2.8.0) - public_suffix (>= 2.0.2, < 5.0) - airbrussh (1.4.1) - sshkit (>= 1.6.1, != 1.7.0) - 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) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + uri (>= 0.13.1) + addressable (2.8.8) + public_suffix (>= 2.0.2, < 8.0) + ast (2.4.3) + aws-eventstream (1.4.0) + aws-partitions (1.1204.0) + aws-sdk-core (3.241.3) + aws-eventstream (~> 1, >= 1.3.0) + aws-partitions (~> 1, >= 1.992.0) + aws-sigv4 (~> 1.9) + base64 + bigdecimal 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) + logger + aws-sdk-kms (1.120.0) + aws-sdk-core (~> 3, >= 3.241.3) + aws-sigv4 (~> 1.5) + aws-sdk-s3 (1.211.0) + aws-sdk-core (~> 3, >= 3.241.3) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.4) - aws-sigv4 (1.5.1) + aws-sigv4 (~> 1.5) + aws-sigv4 (1.12.1) aws-eventstream (~> 1, >= 1.0.2) - builder (3.2.4) - cancancan (3.4.0) - capistrano (3.17.0) - airbrussh (>= 1.0.0) - i18n - rake (>= 10.0.0) - sshkit (>= 1.9.0) - capistrano-bundler (2.1.0) - capistrano (~> 3.1) - capistrano-passenger (0.2.1) - capistrano (~> 3.0) - capistrano-rails (1.6.2) - capistrano (~> 3.1) - capistrano-bundler (>= 1.1, < 3) - capistrano-rbenv (2.2.0) - capistrano (~> 3.1) - sshkit (~> 1.3) - carrierwave (1.3.2) + base64 (0.3.0) + benchmark (0.5.0) + bigdecimal (4.0.1) + builder (3.3.0) + cancancan (3.6.1) + carrierwave (1.3.4) activemodel (>= 4.0.0) activesupport (>= 4.0.0) mime-types (>= 1.16) - ssrf_filter (~> 1.0) + ssrf_filter (~> 1.0, < 1.1.0) case_transform (0.2) activesupport - 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) + concurrent-ruby (1.3.6) + connection_pool (3.0.2) + crack (1.0.1) + bigdecimal rexml crass (1.0.6) - database_cleaner (2.0.1) - database_cleaner-active_record (~> 2.0.0) - database_cleaner-active_record (2.0.1) + csv (3.3.5) + database_cleaner (2.1.0) + database_cleaner-active_record (>= 2, < 3) + database_cleaner-active_record (2.2.2) activerecord (>= 5.a) - database_cleaner-core (~> 2.0.0) + database_cleaner-core (~> 2.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) + date (3.5.1) + diff-lcs (1.6.2) + drb (2.2.3) + elasticsearch (7.17.11) + elasticsearch-api (= 7.17.11) + elasticsearch-transport (= 7.17.11) + elasticsearch-api (7.17.11) + multi_json + elasticsearch-transport (7.17.11) + base64 + faraday (>= 1, < 3) + multi_json + erb (6.0.1) + erubi (1.13.1) + factory_bot (6.5.6) + activesupport (>= 6.1.0) + factory_bot_rails (6.5.1) + factory_bot (~> 6.5) + railties (>= 6.1.0) + faker (3.5.3) 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) + faraday (2.14.0) + faraday-net_http (>= 2.0, < 3.5) + json + logger + faraday-httpclient (2.0.2) + httpclient (>= 2.2) + faraday-net_http (3.4.2) + net-http (~> 0.5) + ferrum (0.17.1) addressable (~> 2.5) - cliver (~> 0.3) + base64 (~> 0.2) 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) - http-accept (1.7.0) - http-cookie (1.0.5) - domain_name (~> 0.5) - httparty (0.20.0) - mime-types (~> 3.0) + webrick (~> 1.7) + websocket-driver (~> 0.7) + ffi (1.17.3-aarch64-linux-gnu) + ffi (1.17.3-arm64-darwin) + globalid (1.3.0) + activesupport (>= 6.1) + hashdiff (1.2.1) + hashie (5.1.0) + logger + httparty (0.24.0) + csv + mini_mime (>= 1.0.0) multi_xml (>= 0.5.2) - httpclient (2.8.3) - i18n (1.12.0) + httpclient (2.9.0) + mutex_m + i18n (1.14.8) concurrent-ruby (~> 1.0) - image_processing (1.12.2) - mini_magick (>= 4.9.5, < 5) + image_processing (1.14.0) + mini_magick (>= 4.9.5, < 6) ruby-vips (>= 2.0.17, < 3) - ipinfo-rails (1.0.1) - IPinfo (~> 1.0.1) + io-console (0.8.2) + ipinfo-rails (1.2.0) + IPinfo (~> 2.4) rack (~> 2.0) - jmespath (1.6.1) - json (2.6.2) + irb (1.16.0) + pp (>= 0.6.0) + rdoc (>= 4.0.0) + reline (>= 0.4.2) + jmespath (1.6.2) + json (2.18.0) jsonapi-renderer (0.2.2) - jwt (2.4.1) + jwt (2.2.2) kaminari (1.2.2) activesupport (>= 4.1.0) kaminari-actionview (= 1.2.2) @@ -216,220 +216,288 @@ GEM activerecord kaminari-core (= 1.2.2) kaminari-core (1.2.2) - listen (3.7.1) + language_server-protocol (3.17.0.5) + lint_roller (1.1.0) + listen (3.9.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) - loofah (2.18.0) + logger (1.7.0) + loofah (2.25.0) crass (~> 1.0.2) - nokogiri (>= 1.5.9) + nokogiri (>= 1.12.0) lru_redux (1.1.0) - mail (2.7.1) + mail (2.9.0) + logger mini_mime (>= 0.1.1) - marcel (1.0.2) - method_source (1.0.0) - mime-types (3.4.1) - mime-types-data (~> 3.2015) - 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.2.3) - mysql2 (0.5.4) - net-imap (0.3.1) + net-imap + net-pop + net-smtp + marcel (1.1.0) + mime-types (3.7.0) + logger + mime-types-data (~> 3.2025, >= 3.2025.0507) + mime-types-data (3.2026.0113) + mini_magick (5.3.1) + logger + mini_mime (1.1.5) + minitest (6.0.1) + prism (~> 1.5) + mize (0.6.1) + multi_json (1.19.1) + multi_xml (0.8.1) + bigdecimal (>= 3.1, < 5) + multipart-post (2.4.1) + mutex_m (0.3.0) + net-http (0.9.1) + uri (>= 0.11.1) + net-imap (0.6.2) + date net-protocol net-pop (0.1.2) net-protocol - net-protocol (0.1.3) + net-protocol (0.2.2) timeout - net-scp (1.2.1) - net-ssh (>= 2.6.5) - net-smtp (0.3.2) + net-smtp (0.5.1) 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) + nio4r (2.7.5) + nokogiri (1.19.0-aarch64-linux-gnu) racc (~> 1.4) - nokogiri (1.13.8-x86_64-linux) + nokogiri (1.19.0-arm64-darwin) racc (~> 1.4) - oauth (0.5.10) - parallel (1.22.1) - pg (1.4.2) - public_suffix (4.0.7) + oauth (1.1.3) + base64 (~> 0.1) + oauth-tty (~> 1.0, >= 1.0.6) + snaky_hash (~> 2.0) + version_gem (~> 1.1, >= 1.1.9) + oauth-tty (1.0.6) + version_gem (~> 1.1, >= 1.1.9) + parallel (1.27.0) + parser (3.3.10.1) + ast (~> 2.4.1) + racc + pg (1.6.3-aarch64-linux) + pg (1.6.3-arm64-darwin) + pp (0.6.3) + prettyprint + prettyprint (0.2.0) + prism (1.8.0) + psych (5.3.1) + date + stringio + public_suffix (6.0.2) puma (4.3.12) nio4r (~> 2.0) - racc (1.6.0) - rack (2.2.4) - rack-cors (1.1.1) + racc (1.8.1) + rack (2.2.21) + rack-cors (2.0.2) rack (>= 2.0.0) - rack-test (2.0.2) + rack-session (1.0.2) + rack (< 3) + rack-test (2.2.0) 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) + rackup (1.0.1) + rack (< 3) + webrick + rails (8.0.4) + actioncable (= 8.0.4) + actionmailbox (= 8.0.4) + actionmailer (= 8.0.4) + actionpack (= 8.0.4) + actiontext (= 8.0.4) + actionview (= 8.0.4) + activejob (= 8.0.4) + activemodel (= 8.0.4) + activerecord (= 8.0.4) + activestorage (= 8.0.4) + activesupport (= 8.0.4) bundler (>= 1.15.0) - railties (= 6.1.6.1) - sprockets-rails (>= 2.0.0) - rails-dom-testing (2.0.3) - activesupport (>= 4.2.0) + railties (= 8.0.4) + rails-dom-testing (2.3.0) + activesupport (>= 5.0.0) + minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.4.3) - loofah (~> 2.3) - railties (6.1.6.1) - actionpack (= 6.1.6.1) - activesupport (= 6.1.6.1) - method_source + rails-html-sanitizer (1.6.2) + loofah (~> 2.21) + nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) + railties (8.0.4) + actionpack (= 8.0.4) + activesupport (= 8.0.4) + irb (~> 1.13) + rackup (>= 1.0.0) rake (>= 12.2) - thor (~> 1.0) - rake (13.0.6) - rb-fsevent (0.11.1) - rb-inotify (0.10.1) + thor (~> 1.0, >= 1.2.2) + tsort (>= 0.2) + zeitwerk (~> 2.6) + rainbow (3.1.1) + rake (13.3.1) + rb-fsevent (0.11.2) + rb-inotify (0.11.1) ffi (~> 1.0) + rdoc (7.1.0) + erb + psych (>= 4.0.0) + tsort + readline (0.0.4) + reline redis (3.3.5) - 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) + redis-client (0.26.3) + connection_pool + regexp_parser (2.11.3) + reline (0.6.3) + io-console (~> 0.5) + rexml (3.4.4) + rgeo (3.0.1) + ros-apartment (3.4.1) + activerecord (>= 7.0.0, < 8.2) + activesupport (>= 7.0.0, < 8.2) 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) + public_suffix (>= 2.0.5, < 7) + rack (>= 1.3.6, < 4.0) + rspec-core (3.13.6) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.5) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.11.0) - rspec-mocks (3.11.1) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.7) diff-lcs (>= 1.2.0, < 2.0) - 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) + rspec-support (~> 3.13.0) + rspec-rails (6.1.5) + actionpack (>= 6.1) + activesupport (>= 6.1) + railties (>= 6.1) + rspec-core (~> 3.13) + rspec-expectations (~> 3.13) + rspec-mocks (~> 3.13) + rspec-support (~> 3.13) + rspec-support (3.13.6) + rubocop (1.82.1) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) + parallel (~> 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.48.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.49.0) + parser (>= 3.3.7.2) + prism (~> 1.7) + rubocop-performance (1.26.1) + lint_roller (~> 1.1) + rubocop (>= 1.75.0, < 2.0) + rubocop-ast (>= 1.47.1, < 2.0) + rubocop-rails (2.34.3) + activesupport (>= 4.2.0) + lint_roller (~> 1.1) + rack (>= 1.1) + rubocop (>= 1.75.0, < 2.0) + rubocop-ast (>= 1.44.0, < 2.0) + rubocop-rails-omakase (1.1.0) + rubocop (>= 1.72) + rubocop-performance (>= 1.24) + rubocop-rails (>= 2.30) + ruby-progressbar (1.13.0) + ruby-vips (2.3.0) ffi (~> 1.12) - ruby2_keywords (0.0.5) + logger + searchkick (6.0.3) + activemodel (>= 7.2) + securerandom (0.4.1) shoulda-matchers (4.5.1) activesupport (>= 4.2.0) - simplecov (0.21.2) - docile (~> 1.1) - 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) + sidekiq (7.3.9) + base64 + connection_pool (>= 2.3.0) + logger + rack (>= 2.2.4) + redis-client (>= 0.22.2) + snaky_hash (2.0.3) + hashie (>= 0.1.0, < 6) + version_gem (>= 1.1.8, < 3) spring (2.1.1) spring-watcher-listen (2.0.1) listen (>= 2.7, < 4.0) spring (>= 1.2, < 3.0) - sprockets (4.1.1) - concurrent-ruby (~> 1.0) - rack (> 1, < 3) - sprockets-rails (3.4.2) - actionpack (>= 5.2) - activesupport (>= 5.2) - sprockets (>= 3.0.0) - sshkit (1.21.2) - net-scp (>= 1.1.2) - net-ssh (>= 2.8.0) - ssrf_filter (1.0.7) + ssrf_filter (1.0.8) + stringio (3.2.0) sync (0.5.0) - term-ansicolor (1.7.1) - tins (~> 1.0) - thor (1.2.1) - timeout (0.3.0) - tins (1.31.1) + term-ansicolor (1.11.3) + tins (~> 1) + thor (1.5.0) + timeout (0.6.0) + tins (1.51.1) + bigdecimal + mize (~> 0.6) + readline sync - tzinfo (2.0.5) + tsort (0.2.0) + tzinfo (2.0.6) concurrent-ruby (~> 1.0) - unf (0.1.4) - unf_ext - unf_ext (0.0.8.2) + unicode-display_width (3.2.0) + unicode-emoji (~> 4.1) + unicode-emoji (4.2.0) + uri (1.1.1) + useragent (0.16.11) + version_gem (1.1.9) 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.14.0) + webmock (3.26.1) addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) - websocket-driver (0.7.5) + webrick (1.9.2) + websocket-driver (0.8.0) + base64 websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) - youtube_rails (1.2.2) - yt (0.33.4) + youtube_rails (1.3.0) + yt (0.34.1) activesupport - zeitwerk (2.6.0) + zeitwerk (2.7.4) PLATFORMS 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.12) aws-sdk-s3 (~> 1) - capistrano-passenger - capistrano-rails - capistrano-rbenv (~> 2.0) carrierwave (~> 1.0) - coveralls database_cleaner ecds_rails_auth_engine! + elasticsearch (~> 7.17.1) factory_bot factory_bot_rails faker + faraday-httpclient (~> 2.0) 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 (~> 6.1.0) + rails (~> 8.0.0) redis (~> 3.0) rgeo - ros-apartment - rspec-rails (~> 5.1.2) + ros-apartment (~> 3.4.0) + rspec-rails (~> 6.1.0) + rubocop-rails-omakase + searchkick shoulda-matchers (~> 4.5.1) - simplecov - simplecov-lcov + sidekiq (>= 7.2.2, < 8) spring spring-watcher-listen (~> 2.0.0) term-ansicolor @@ -440,4 +508,4 @@ DEPENDENCIES yt BUNDLED WITH - 2.2.22 + 2.3.7 diff --git a/app/controllers/v3_controller.rb b/app/controllers/v3_controller.rb index d87c08df..20b1e702 100644 --- a/app/controllers/v3_controller.rb +++ b/app/controllers/v3_controller.rb @@ -2,8 +2,8 @@ class V3Controller < ApplicationController include EcdsRailsAuthEngine::CurrentUser - before_action :allowed?, only: [:show, :create, :update, :destroy] - before_action :set_record, only: [:show, :update, :destroy] + before_action :allowed?, only: [ :show, :create, :update, :destroy ] + before_action :set_record, only: [ :show, :update, :destroy ] # GET //1 def show @@ -43,7 +43,7 @@ def serialize_errors errors.push({ detail: error, source: { - pointer: 'data/attributes' + pointer: "data/attributes" } }) end diff --git a/app/controllers/v4/admin/crud_controller.rb b/app/controllers/v4/admin/crud_controller.rb new file mode 100644 index 00000000..8d92383c --- /dev/null +++ b/app/controllers/v4/admin/crud_controller.rb @@ -0,0 +1,37 @@ +module V4 + module Admin + class CrudController < V4Controller + def create + head 401 unless crud_allowed? + end + + def update + head 401 unless crud_allowed? + + if @related_record + if params[:related_type] == "belongs_to" + @record.update( + params[:attribute].to_sym => @related_record + ) + end + else + @record.update( + params[:attribute].to_sym => params[:value] + ) + end + end + + def set_record + id = params[:id] + model = params[:model].titleize.constantize + @record = model.find(id) + set_related_record if params[:related_model] + end + + def set_related_record + related_model = params[:related_model].titleize.constantize + @related_record = related_model.find(params[:value].to_i) + end + end + end +end diff --git a/app/controllers/v4/public/media_controller.rb b/app/controllers/v4/public/media_controller.rb new file mode 100644 index 00000000..3d6c91a8 --- /dev/null +++ b/app/controllers/v4/public/media_controller.rb @@ -0,0 +1,22 @@ +module V4 + module Public + class MediaController < V4Controller + def show + return 404 if @record.nil? + + if params[:variant] + redirect_to @record.files[params[:variant].to_sym], allow_other_host: true + else + redirect_to @record.file.blob.url, allow_other_host: true + end + end + + private + + def set_record + blob = ActiveStorage::Blob.find_by(key: params[:key]) + @record = ActiveStorage::Attachment.find_by(blob:)&.record + end + end + end +end diff --git a/app/controllers/v4/public/stops_controller.rb b/app/controllers/v4/public/stops_controller.rb new file mode 100644 index 00000000..048b446e --- /dev/null +++ b/app/controllers/v4/public/stops_controller.rb @@ -0,0 +1,14 @@ +module V4 + module Public + class StopsController < V4Controller + def index + @records = if (params[:slug]) + Stop.search(params[:slug], fields: [:slug], load: false).first + else + Array(Stop.search('*', load: false)) + end + render json: { data: @records } + end + end + end +end \ No newline at end of file diff --git a/app/controllers/v4/public/tour_sets_controller.rb b/app/controllers/v4/public/tour_sets_controller.rb new file mode 100644 index 00000000..0f0ea75a --- /dev/null +++ b/app/controllers/v4/public/tour_sets_controller.rb @@ -0,0 +1,9 @@ +module V4 + module Public + class TourSetsController < V4Controller + def index + render json: { data: Array(TourSet.search('*', load: false, limit: TourSet.count)).sort_by(&:name) } + end + end + end +end \ No newline at end of file diff --git a/app/controllers/v4/public/tours_controller.rb b/app/controllers/v4/public/tours_controller.rb new file mode 100644 index 00000000..aa574a73 --- /dev/null +++ b/app/controllers/v4/public/tours_controller.rb @@ -0,0 +1,46 @@ +module V4 + module Public + class ToursController < V3::ToursController + def index + @records = if params[:slug] + record = Tour.search(params[:slug], fields: [ :slug ], limit: 1, load: false).first + record if record.published || crud_allowed?(record) + elsif current_user&.current_tenant_admin? + Array(Tour.search("*", load: false, limit: Tour.count)) if current_user&.current_tenant_admin? + elsif current_user + user_tours = current_user.tours.map { |t| Tour.search("*", load: false, limit: 1).where(id: t.id).first } if current_user + ( + user_tours + + Array(Tour.search("*", load: false, limit: Tour.published.count).where(published: true)) + ).uniq + else + Array(Tour.search("*", load: false, limit: Tour.published.count).where(published: true)) + end + + render json: { data: { title: "...." } } if @records.nil? + render json: { data: @records } unless @records.nil? + end + + def create + raise NotImplementedError, "Create is not implemented in V4" + end + + def update + raise NotImplementedError, "Update is not implemented in V4" + end + + def destroy + raise NotImplementedError, "Destroy is not implemented in V4" + end + + def crud_allowed?(record = @record) + tour = Tour.find(Integer(record.id)) unless record.nil? + current_user&.current_tenant_admin? || current_user.tours.include?(tour) + end + + def set_record + @record = Tour.search("*").where(id: params[:id]).limit(1).load(false).first + end + end + end +end diff --git a/app/controllers/v4_controller.rb b/app/controllers/v4_controller.rb new file mode 100644 index 00000000..9e297d61 --- /dev/null +++ b/app/controllers/v4_controller.rb @@ -0,0 +1,3 @@ +class V4Controller < V3Controller + +end \ No newline at end of file diff --git a/app/controllers/welcome_controller.rb b/app/controllers/welcome_controller.rb new file mode 100644 index 00000000..8679a69f --- /dev/null +++ b/app/controllers/welcome_controller.rb @@ -0,0 +1,5 @@ +class WelcomeController < ActionController::Base + def index + render json: {} + end +end \ No newline at end of file diff --git a/app/jobs/reindex_job.rb b/app/jobs/reindex_job.rb new file mode 100644 index 00000000..68488780 --- /dev/null +++ b/app/jobs/reindex_job.rb @@ -0,0 +1,10 @@ +class ReindexJob < ApplicationJob + queue_as :searchkick + + def perform(tenant:, class_name:, id:) + Apartment::Tenant.switch! tenant + model = class_name.constantize + record = model.find(id) + record.reindex if record.present? + end +end diff --git a/app/models/application_record.rb b/app/models/application_record.rb index ef998df7..cd59d007 100644 --- a/app/models/application_record.rb +++ b/app/models/application_record.rb @@ -3,4 +3,8 @@ # Base class for models. class ApplicationRecord < ActiveRecord::Base self.abstract_class = true + + def mappings + {} + end end diff --git a/app/models/concerns/html_saintizer.rb b/app/models/concerns/html_sanitizer.rb similarity index 62% rename from app/models/concerns/html_saintizer.rb rename to app/models/concerns/html_sanitizer.rb index d3916421..aa369d23 100644 --- a/app/models/concerns/html_saintizer.rb +++ b/app/models/concerns/html_sanitizer.rb @@ -2,14 +2,14 @@ # frozen_string_literal: true # app/models/concerns/html_saintizer.rb -module HtmlSaintizer +module HtmlSanitizer extend ActiveSupport::Concern - def self.accessable(text) + def self.accessible(text) Rails::Html::FullSanitizer.new.sanitize(text).to_s.gsub(/([A-z]\.)([A-z])/, '\1 \2') end - def self.accessable_truncated(text) - self.accessable(text).truncate(140) + def self.accessible_truncated(text) + self.accessible(text).truncate(140) end end diff --git a/app/models/concerns/searchable.rb b/app/models/concerns/searchable.rb new file mode 100644 index 00000000..f97ca458 --- /dev/null +++ b/app/models/concerns/searchable.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +# Common stuff for indexing +module Searchable + extend ActiveSupport::Concern + + included do + searchkick index_name: -> { "otb_#{Apartment::Tenant.current}_#{model_name.plural}_#{Rails.env}" }, + callbacks: false, + deep_paging: true + + after_commit :reindex_record + end + + def medium_index(m, current_tenant) + http_path = "#{Rails.application.routes.default_url_options[:host]}/#{current_tenant}/v4/public/media/#{m.medium.file.key}" + { + caption: m.medium.caption, + desktop_width: m.medium.desktop_width, + embed: m.medium.embed, + filename: m.medium.filename, + files: { + original: http_path, + mobile: "#{http_path}?variant=mobile", + tablet: "#{http_path}?variant=tablet", + desktop: "#{http_path}?variant=desktop", + lqip: "#{http_path}?variant=lqip" + }, + id: m.medium.id, + lqip_width: m.medium.lqip_width, + mobile_width: m.medium.mobile_width, + original_image: m.medium.original_image, + position: m.position, + provider: m.medium.provider, + tablet_width: m.medium.tablet_width, + title: m.medium.title, + video: m.medium.video + } + end + + private + + def reindex_record + ReindexJob.perform_later(tenant: Apartment::Tenant.current, class_name: self.class.name, id:) + end +end diff --git a/app/models/medium.rb b/app/models/medium.rb index 48cdca2c..f703aa60 100644 --- a/app/models/medium.rb +++ b/app/models/medium.rb @@ -21,8 +21,8 @@ class Medium < MediumBaseRecord has_many :tour_media has_many :tours, through: :tour_media - enum video_provider: { keiner: 0, vimeo: 1, youtube: 2, soundcloud: 3 } - + # enum video_provider: { keiner: 0, vimeo: 1, youtube: 2, soundcloud: 3 } + enum :video_provider, { :keiner=>0, :vimeo=>1, :youtube=>2, :soundcloud=>3} attr_accessor :insecure def props @@ -40,10 +40,10 @@ def files 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 + lqip: file.url, + mobile: file.url, + tablet: file.url, + desktop: file.url } end { @@ -67,9 +67,9 @@ def replace_video def add_widths return unless file.attached? - 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 + self.lqip_width = 50 + self.mobile_width = 300 + self.tablet_width = 400 + self.desktop_width = 750 end end diff --git a/app/models/medium_base_record.rb b/app/models/medium_base_record.rb index 64f0e8c9..876f3271 100755 --- a/app/models/medium_base_record.rb +++ b/app/models/medium_base_record.rb @@ -11,7 +11,7 @@ class MediumBaseRecord < ApplicationRecord validates_presence_of :filename # has_one_attached "#{Apartment::Tenant.current.underscore}_file" - has_one_attached 'file' + has_one_attached "file" attr_accessor :content_type @@ -22,7 +22,7 @@ class MediumBaseRecord < ApplicationRecord # end def tmp_file_path - return Rails.root.join('public', 'storage', 'tmp', filename) if self.filename + return Rails.root.join("public", "storage", "tmp", filename) if self.filename nil end @@ -45,8 +45,9 @@ def attach_file # file.blob.delete if file.attached? self.parse_base64 - File.open(tmp_file_path, 'wb') do |f| + File.open(tmp_file_path, "wb") do |f| f.write(Base64.decode64(base_sixty_four)) + f.rewind end self.file.attach( @@ -59,7 +60,7 @@ def attach_file end def remove_tmp_file - File.delete(tmp_file_path) if File.exists?(tmp_file_path) + File.delete(tmp_file_path) if File.exist?(tmp_file_path) end def purge @@ -72,20 +73,20 @@ def check_content_type 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.') + 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(',') + 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 + self.content_type = Regexp.last_match(1).split(";base64").first else - self.content_type = 'image/jpeg' + self.content_type = "image/jpeg" end end end diff --git a/app/models/stop.rb b/app/models/stop.rb index 3069a1c1..46b25d69 100644 --- a/app/models/stop.rb +++ b/app/models/stop.rb @@ -2,7 +2,8 @@ # Model class for a tour stop. class Stop < ApplicationRecord - include HtmlSaintizer + include HtmlSanitizer + include Searchable has_many :tour_stops, dependent: :destroy has_many :tours, -> { distinct }, through: :tour_stops @@ -12,7 +13,7 @@ class Stop < ApplicationRecord belongs_to :map_icon, optional: true has_many :stop_slugs, dependent: :delete_all - before_validation -> { self.title ||= 'untitled' } + before_validation -> { self.title ||= "untitled" } validates :title, presence: true @@ -20,18 +21,18 @@ class Stop < ApplicationRecord before_create :ensure_icon_color after_save :ensure_slug - 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) } + 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 - HtmlSaintizer.accessable(description) + HtmlSanitizer.accessible(description) end def sanitized_direction_notes - HtmlSaintizer.accessable(direction_notes) + HtmlSanitizer.accessible(direction_notes) end def slug - title ? title.parameterize_intl : '' + title ? title.parameterize_intl : "" end def splash @@ -57,10 +58,41 @@ def published tours.any? { |tour| tour.published } end + def should_index? + !orphaned + end + + def search_data + { + address:, + article_link:, + description:, + direction_intro:, + direction_notes:, + icon: map_icon&.original_image_url, + icon_color:, + id:, + lat: lat&.to_f, + lng: lng&.to_f, + map_icon: map_icon&.original_image_url || nil, + media: media_index, + meta_description:, + parking_lat: parking_lat&.to_f, + parking_lng: parking_lng&.to_f, + sanitized_description:, + slug:, + splash:, + title:, + type: "stop", + video_embed:, + video_poster: + } + end + private def default_values - self.meta_description ||= HtmlSaintizer.accessable_truncated(self.description) + self.meta_description ||= HtmlSanitizer.accessible_truncated(self.description) end def ensure_slug @@ -68,6 +100,14 @@ def ensure_slug end def ensure_icon_color - self.icon_color = '#D32F2F' if icon_color.nil? + self.icon_color = "#D32F2F" if icon_color.nil? + end + + def media_index + indexed_media = stop_media.map do |m| + medium_index(m, tours.first.tenant) + end + + indexed_media.sort_by { |m| m[:position] } end end diff --git a/app/models/tour.rb b/app/models/tour.rb index d6116d49..02c55fdc 100644 --- a/app/models/tour.rb +++ b/app/models/tour.rb @@ -1,11 +1,12 @@ # frozen_string_literal: true -require 'uri' +require "uri" # Model class for a tour. class Tour < ApplicationRecord - include HtmlSaintizer - + include ActionView::Helpers::DateHelper + include HtmlSanitizer + include Searchable has_many :tour_stops, autosave: true, dependent: :destroy has_many :stops, -> { distinct }, through: :tour_stops @@ -22,24 +23,15 @@ class Tour < ApplicationRecord has_many :slugs, dependent: :delete_all 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 } - 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 - } + 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 -> { self.title ||= "untitled" } before_validation :update_saved_stop_order before_save :calculate_duration before_save :check_url @@ -52,7 +44,7 @@ class Tour < ApplicationRecord scope :has_stops, -> { includes(:stops).where.not(stops: { id: nil }) } def sanitized_description - HtmlSaintizer.accessable(description) + HtmlSanitizer.accessible(description) end def slug @@ -67,13 +59,6 @@ def tenant_title Apartment::Tenant.current.titleize end - # def external_url - # if Apartment::Tenant.current == 'public' - # return nil - # end - # TourSet.find_by(subdir: Apartment::Tenant.current).external_url - # end - def theme_title theme.title end @@ -142,7 +127,7 @@ def calculate_duration 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] } + 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. @@ -155,6 +140,40 @@ def calculate_duration self.duration = durations.compact.sum.zero? ? nil : durations.sum end + def search_data + { + blank_map:, + bounds:, + default_lng:, + description:, + est_time: duration ? "#{distance_of_time_in_words(duration).capitalize} #{mode.title.downcase}" : nil, + flat_pages: tour_flat_pages.map { |fp| { id: fp.flat_page.id, title: fp.flat_page.title, position: fp.position, slug: fp.flat_page.slug, body: fp.flat_page.body } }.sort_by { |fp| fp[:position] }, + id:, + is_geo:, + link_address:, + link_text:, + map_overlay: map_overlay_index, + map_type: map_type || "hybrid", + media: media_index, + meta_description:, + modes: modes.map { |m| { id: m.id, title: m.title, icon: m.icon, default: m == self.mode } }, + published:, + restrict_bounds:, + restrict_bounds_to_overlay:, + sanitized_description:, + splash:, + slug:, + stop_count:, + stops: stop_index, + tenant:, + tenant_title:, + title:, + theme: { id: theme.id, title: theme.title }, + type: "tour", + use_directions: + } + end + private def ensure_slug @@ -193,4 +212,38 @@ def check_for_overlay self.restrict_bounds_to_overlay = false end end + + def media_index + indexed_media = tour_media.map do |m| + medium_index(m, tenant) + end + + indexed_media.sort_by { |m| m[:position] } + end + + def stop_index + indexed_stops = tour_stops.map do |ts| + { + next: ts.next.present? ? { id: ts.next.stop.id, slug: ts.next.stop.slug, title: ts.next.stop.title } : nil, + position: ts.position, + previous: ts.previous.present? ? { id: ts.previous.stop.id, slug: ts.previous.stop.slug, title: ts.previous.stop.title } : nil, + **ts.stop.search_data + } + end + + indexed_stops.sort_by { |s| s[:position] } + end + + def map_overlay_index + return unless map_overlay.present? + + { + id: map_overlay.id, + east: map_overlay.east.to_f, + image_url: map_overlay.original_image_url, + north: map_overlay.north.to_f, + south: map_overlay.south.to_f, + west: map_overlay.west.to_f + } + end end diff --git a/app/models/tour_set.rb b/app/models/tour_set.rb index ad205817..664fb6f6 100644 --- a/app/models/tour_set.rb +++ b/app/models/tour_set.rb @@ -2,6 +2,8 @@ # Model class for tour sets. This is the main model for "instances" of Open Tour Builder. class TourSet < ApplicationRecord + include Searchable + before_save :set_subdir before_save :attach_file after_create :create_tenant @@ -24,13 +26,14 @@ def published_tours Tour.published.has_stops.each do |t| tour = { title: t.title, - slug: t.slug + slug: t.slug, + location: { lat: t.bounds[:centerLat], lng: t.bounds[:centerLng] } } tours.push(tour) end tours - rescue Apartment::TenantNotFound => error - # self.delete + rescue Apartment::TenantNotFound => _ + logger.warn('Tenant not found.') end end @@ -47,8 +50,8 @@ def mapable_tours tours.push(tour) end tours - rescue Apartment::TenantNotFound => error - # self.delete + rescue Apartment::TenantNotFound => _ + logger.warn('Tenant not found.') end end @@ -63,6 +66,23 @@ def logo_url nil end + def should_index? + published_tours.count > 0 + end + + def search_data + { + external_url:, + footer_logo:, + name:, + logo_url:, + mapable_tours:, + published_tours:, + notes:, + subdir:, + } + end + private def set_subdir @@ -75,15 +95,15 @@ def create_tenant # auth engine. Hopfully this will be replaced when we # redo the auth engine. Apartment::Tenant.reset - schemas = ActiveRecord::SchemaMigration.all - - schemas.each do |schema| - Apartment::Tenant.switch!(subdir) - migration = ActiveRecord::SchemaMigration.find_by_version(schema.version) - if migration.nil? - ActiveRecord::SchemaMigration.create(version: schema.version) - end - end + # schemas = ActiveRecord::SchemaMigration.all + + # schemas.each do |schema| + # Apartment::Tenant.switch!(subdir) + # migration = ActiveRecord::SchemaMigration.find_by_version(schema.version) + # if migration.nil? + # ActiveRecord::SchemaMigration.create(version: schema.version) + # end + # end end def create_defaults @@ -114,7 +134,7 @@ def tmp_file_path # # Create and attach file from Base64 string. # - # This should only be called once when a new medium obeject is created via the API + # This should only be called once when a new medium object 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 @@ -129,7 +149,7 @@ def attach_file if base_sixty_four.nil? && logo.attached? logo.purge else - headers, self.base_sixty_four = base_sixty_four.split(',') + _, self.base_sixty_four = base_sixty_four.split(',') return if base_sixty_four.nil? diff --git a/build.sh b/build.sh index ef706fc2..17406364 100755 --- a/build.sh +++ b/build.sh @@ -1,29 +1,30 @@ #!/bin/bash set -e -TAG=$([ "$BRANCH" == "main" ] && echo "stable" || echo "latest") +# TAG=$([ "$BRANCH" == "main" ] && echo "stable" || echo "latest") -AWS_ECS_CLUSTER=$([ "$BRANCH" == "main" ] && echo "$AWS_ECS_CLUSTER_PROD" || echo "$AWS_ECS_CLUSTER_DEV") +# 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") +# AWS_ECS_SERVICE=$([ "$BRANCH" == "main" ] && echo "$AWS_ECS_SERVICE_PROD" || echo "$AWS_ECS_SERVICE_DEV") -echo "Building image for branch: $BRANCH with tag: $TAG" +# echo "Building image for branch: $BRANCH with tag: $TAG" docker build \ + --platform linux/amd64 \ --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}" + docker login --username AWS --password-stdin 310867200447.dkr.ecr.us-east-1.amazonaws.com echo "Logged in successfully" echo "Tagging image with $TAG" -docker tag otb "${AWS_ECR}/otb:${TAG}" +docker tag otb 310867200447.dkr.ecr.us-east-1.amazonaws.com/otb:latest echo "Pushing image" -docker push "${AWS_ECR}/otb:${TAG}" +docker push 310867200447.dkr.ecr.us-east-1.amazonaws.com/otb:latest 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 +aws ecs update-service --cluster otb-dev --service otb-dev --force-new-deployment --region us-east-1 \ No newline at end of file diff --git a/config/application.rb b/config/application.rb index 3933417c..18fc7e5c 100644 --- a/config/application.rb +++ b/config/application.rb @@ -1,21 +1,21 @@ # frozen_string_literal: true -require_relative 'boot' +require_relative "boot" -require 'rails' +require "rails" # Pick the frameworks you want: -require 'active_model/railtie' -require 'active_job/railtie' -require 'active_record/railtie' -require 'action_controller/railtie' -require 'action_mailer/railtie' -require 'action_view/railtie' -require 'action_cable/engine' +require "active_model/railtie" +require "active_job/railtie" +require "active_record/railtie" +require "action_controller/railtie" +require "action_mailer/railtie" +require "action_view/railtie" +require "action_cable/engine" # require "sprockets/railtie" -require 'rails/test_unit/railtie' -require 'apartment/elevators/generic' -require 'active_storage/engine' -require 'ipinfo-rails' +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,16 +27,16 @@ class Application < Rails::Application class DirectoryElevator < Apartment::Elevators::Generic def parse_tenant_name(request) # request is an instance of Rack::Request - tenant_name = request.fullpath.split('/')[1] + tenant_name = request.fullpath.split("/")[1] - if tenant_name == 'auth' || tenant_name == 'rails' + if tenant_name == "auth" || tenant_name == "rails" || tenant_name == "sidekiq" return nil end tenant_name end end # Initialize configuration defaults for originally generated Rails version. - config.load_defaults 5.1 + config.load_defaults 7.0 # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers @@ -45,13 +45,13 @@ def parse_tenant_name(request) 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) }) + 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. config.api_only = true config.middleware.use DirectoryElevator - # config.relative_url_root = '/*' + config.active_job.queue_adapter = :sidekiq config end end diff --git a/config/boot.rb b/config/boot.rb index 30e594e2..3aea51f6 100644 --- a/config/boot.rb +++ b/config/boot.rb @@ -3,3 +3,4 @@ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) require 'bundler/setup' # Set up gems listed in the Gemfile. +require 'logger' \ No newline at end of file diff --git a/config/credentials.yml.enc b/config/credentials.yml.enc new file mode 100644 index 00000000..a5c93672 --- /dev/null +++ b/config/credentials.yml.enc @@ -0,0 +1 @@ +PRG7YQKgWxRVMotNVw2Hu3zUJrv4hnxJx20t+BsF1znI07Je+o1dUP9txWjR7Bo2w46JjZAICoeap3AT6Vw9Ipf4zZFkWrzaC7wrWE3+LSaSb69x+8wSzYbOSuwz2FEVYCSw8Pn/8srL/aNE3p5EC0TLb1XS7QlWq6Wz6wQApJ3Ih3w3kIXHcSAgtFat0ze514Zma+0a76AXb0wXXV0uQJ/O6hyJ6sHP8CjRwxCmIB3HoVjeTqBeURKU6trMp0Y4vjtY5ZPXYtL47mXyr2SnmhPDp1Wa7oke5z2lJ1JIs95sbkQwnZiw67FY7uU/g4qQXqvCDD2NSBo/5zQftAjFIUKePZKj3oJX1hQBKZEFpMdkjMaVST18GvjlivXwpVcmaK/yDCgm70QiND45nVO/YF7EmC0z+6yZ6nNvuGTRqXIaVzrdduxGu9LWtaNyy5ed8/uFwqZOWxZrpFYp5PwGcRVZ9Ygyt3aF5ybsexugCEvQqaABfzsGanqyyUiR2eG0fbIm6wXvcl+tn2EDvMs//zxmeDvxAn1/JCctqzeIbKw7d3paAmNd4YtcrqIuYGdnx5TlvssuMdYjsLL2kP1w6/l7BBlTAy7fJaecCMY+/5NdS8bEnqGmePX5BLNlSgZxUvu5fAzZFoXNbdpEvLt8csmC8tq5TBN8/YM7uCgGidRiPL2h/v0S/GfHSh4VKQmRnsTypRkK8/2qSHCGBfVAPCTg6haThoHc/30R3soQobhXz0bhTQ262TilHriTmIZ/5ipcQnfqLS1uHAPJnYqwzFAR+koFtr5m7o9EhOUwx2R7GCdpndXT9BZDrcyr+DHZrlQB5gnwDmqo6MfHS994HOyMvudCoYNibZ3Gz6rGvBoZnwLkxz8vJR4l4kH51CijnTsFJ5h9Hecoxex6nT0FjqdOGG/fWeE7U2j9TaxDxW4z0JVxHXiNy3ygcvSXr9X34nSWN7bfR2hPONzAiCj44qajPKYlCmdUFABewtyx1f1FDB+/mLwcfPEHs9Q/ZkBTRnPjvjbOX9SRBgFrIOptpCaZkbj0TH/CcarUdNBQFgXm4Jbv5zx1bIfQUWS3TzOmhruvlU5kzW/fgqEVqJMg7f5HLWTVOryyL/ZzkSgz20wUx4k0KHhWEji5gmQUp4XM8KMp7NwTIbBrCFdpGgVCL9ufu8OeGmLTSd/Aio/q5bVDL6suxZyEtX/lRPBk7WYoUBQ5FrNX1k5zCKobp8ux7TGCilaPS0raLrW1dOuHekNvG7ZTmPZuZa1/1bF35ZPeOuIwbDukotv1DiurEjnVEoU61dmzOa+L3Y2jT4i7K7dfrEV9q8bA+cPA7KXUjxTD7l2wssXeiyKLCAUdn75OwPJyHQ8EcwqkoIPLlJwSnutYBHc5yeM9O7mfEsHvCuBVC3AbNOKMEsV0eda8IQdpul1OjBuIrkag0AmLUSttgK7lIhNHGSy7AfyKBH8L/3CyGeZVnldiFmXFT+JhkYF30e1rsKmiiNM7HOgsIAvJY8Vm4ShLWARN/FE95aTDN9FNY7RqMsjftM8W4U9wlkE47IVGs75lSuVPvFe87Qkb5KuFBlmm9Gziksz1f8XuQYifrJr/C5Q6lTrzRIT8czC666X6+NdjG7/kfdBq3ws/xtTbHqDbwgDM6yJv+0SzHL5Y1DtMVg==--xQB2Ty9pMVA1t66B--jE1kKBwykJFYMd/F80qv8w== \ No newline at end of file diff --git a/config/database.yml b/config/database.yml index faeb8607..254d44cd 100644 --- a/config/database.yml +++ b/config/database.yml @@ -9,14 +9,30 @@ default: &default 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' %> +# 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' %> + password: <%= Rails.application.credentials.dig(:dbTest, :postgres, :pw) %> + username: <%= Rails.application.credentials.dig(:dbTest, :postgres, :user) %> + +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) %> + +development: + <<: *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) %> \ No newline at end of file diff --git a/config/environment.rb b/config/environment.rb index f9f287fd..ca27bcab 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -1,10 +1,11 @@ # frozen_string_literal: true # Load the Rails application. -require_relative 'application' +require_relative "application" # Initialize the Rails application. Rails.application.configure do config.force_ssl = true + config.active_support.to_time_preserves_timezone = :zone end Rails.application.initialize! diff --git a/config/environments/development.rb b/config/environments/development.rb index 747b0597..59f69dda 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -16,7 +16,7 @@ config.consider_all_requests_local = true # Active Storage - config.active_storage.service = :local + config.active_storage.service = :production # Enable/disable caching. By default caching is disabled. if Rails.root.join('tmp/caching-dev.txt').exist? diff --git a/config/environments/production.rb b/config/environments/production.rb index 98b48202..3579fef9 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true Rails.application.configure do - config.hosts << 'api.opentour.site' + config.hosts = nil Rails.application.routes.default_url_options[:host] = 'https://api.opentour.site' # Settings specified here will take precedence over those in config/application.rb. @@ -11,6 +11,8 @@ # Code is not reloaded between requests. config.cache_classes = true + config.force_ssl = false + # Eager load code on boot. This eager loads most of Rails and # your application in memory, allowing both threaded web servers # and those relying on copy on write to perform better. @@ -47,8 +49,8 @@ # Use the lowest log level to ensure availability of diagnostic information # when problems arise. - config.log_level = :debug - + config.log_level = :error + config.active_record.logger = nil # Prepend all log lines with the following tags. config.log_tags = [:request_id] diff --git a/config/environments/test.rb b/config/environments/test.rb index 668a85ce..6bdf2432 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -48,8 +48,8 @@ # 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.active_job.queue_adapter = :test + config.active_storage.service = :test # config.consider_all_requests_local = true # config.action_controller.perform_caching = false # config.host = 'localhost:3030' diff --git a/config/initializers/active_storage_apartment_fix.rb b/config/initializers/active_storage_apartment_fix.rb new file mode 100644 index 00000000..b0db1968 --- /dev/null +++ b/config/initializers/active_storage_apartment_fix.rb @@ -0,0 +1,27 @@ +module ActiveStorageApartmentFix + extend ActiveSupport::Concern + + def key + self[:key] ||= "#{Apartment::Tenant.current}/#{self.class.generate_unique_secure_token(length: 28)}" + end + + class_methods do + def create_before_direct_upload!(**args) + Apartment::Tenant.switch(Apartment::Tenant.current) do + super + end + end + end +end + +Rails.application.config.to_prepare do + ActiveStorage::Blob.prepend(ActiveStorageApartmentFix) + + ActiveRecord::Base.class_eval do + def self.find_signed!(signed_id, purpose: nil) + Apartment::Tenant.switch(Apartment::Tenant.current) do + super + end + end + end +end \ No newline at end of file diff --git a/config/initializers/cors.rb b/config/initializers/cors.rb index f341144d..4a9c3066 100644 --- a/config/initializers/cors.rb +++ b/config/initializers/cors.rb @@ -9,8 +9,7 @@ Rails.application.config.middleware.insert_before 0, Rack::Cors do allow do - origins 'https://lvh.me:4200', 'https://otb.ecdsdev.org', 'https://opentour.site', /.*\.opentour.site/, /.*\.lvh.me:4200/, /.*localhost:3000/, /.*\.urbanspatialhistory.org/ - + origins 'https://lvh.me:4200', 'https://otb.ecdsdev.org', 'https://opentour.site', /.*\.opentour.site/, /.*\.dev\.opentour.site/, /.*\.lvh.me:4200/, /.*localhost:3000/, /.*\.urbanspatialhistory.org/, /.*\.otb\.ecdsdev.org/, /.*\.openworldatlanta.org/ resource '*', headers: :any, methods: [:get, :post, :put, :patch, :delete, :options, :head], diff --git a/config/initializers/elasticsearch.rb b/config/initializers/elasticsearch.rb new file mode 100644 index 00000000..7381e49f --- /dev/null +++ b/config/initializers/elasticsearch.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require 'searchkick' + +timeout = 10 + +Searchkick.client = if ENV.fetch('RAILS_ENV', nil) == 'production' + Elasticsearch::Client.new( + host: 'https://search.ecds.io', + api_key: Rails.application.credentials.dig(:rdsProduction, :es_api_key), + transport_options: { request: { timeout: }, headers: { content_type: 'application/json' } }, + retry_on_failure: 2 + ) + + else + Elasticsearch::Client.new( + host: 'localhost', + transport_options: { request: { timeout: }, headers: { content_type: 'application/json' } } + ) + end diff --git a/config/initializers/rails_deprecation_listener.rb b/config/initializers/rails_deprecation_listener.rb new file mode 100644 index 00000000..2d485958 --- /dev/null +++ b/config/initializers/rails_deprecation_listener.rb @@ -0,0 +1,9 @@ +ActiveSupport::Notifications.subscribe("deprecation.rails") do |_name, _start, _finish, _id, payload| + YourLogService.notify( + message: ["RAILS 7 DEPRECATION WARNINGS"], + deprecation_warning: warn, + stack_trace: payload[:callstack], + gem_name: payload[:gem_name], + deprecation_horizon: payload[:deprecation_horizon] + ) +end \ No newline at end of file diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb new file mode 100644 index 00000000..1156a016 --- /dev/null +++ b/config/initializers/sidekiq.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +Sidekiq.configure_server do |config| + config.redis = { url: "#{Rails.application.credentials.dig(ENV['RAILS_ENV'].to_sym, :redis_url)}/8" } + config.logger.level = Logger::ERROR +end + +Sidekiq.configure_client do |config| + config.redis = { url: "#{Rails.application.credentials.dig(ENV['RAILS_ENV'].to_sym, :redis_url)}/8" } +end diff --git a/config/puma.rb b/config/puma.rb index b2102072..30ecc79a 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -9,7 +9,7 @@ # 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. # diff --git a/config/routes.rb b/config/routes.rb index 8fbfacdf..aae0989c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,41 +1,45 @@ # frozen_string_literal: true -# config/routes.rb -# The subdomain constraint determins the tenant. -# class SubdomainConstraint -# def self.matches?(request) -# subdomains = %w(www admin public) -# request.subdomain.present? && !subdomains.include?(request.subdomain) -# end -# end +require "sidekiq/web" Rails.application.routes.draw do - + root "welcome#index" resources :tour_set_admins - scope ':tenant' do - scope module: :v3, constraints: ApiVersion.new('v3', true) do - resources :tour_authors, path: 'tour-authors' + scope ":tenant" do + scope module: :v3, constraints: ApiVersion.new("v3", true) do + resources :tour_authors, path: "tour-authors" resources :users - 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 :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 :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 + namespace :v4 do + namespace :public do + resources :tours, only: [ :index, :show ] + resources :stops, only: [ :index ] + resources :tour_sets, only: [ :index ], path: "tour-sets" + get "media/:key", to: "media#show" + end + namespace :admin do + resources :crud + end + end end - mount EcdsRailsAuthEngine::Engine, at: '/auth' + mount EcdsRailsAuthEngine::Engine, at: "/auth" + mount Sidekiq::Web => "/sidekiq" end diff --git a/config/sidekiq.yml b/config/sidekiq.yml new file mode 100644 index 00000000..e856281b --- /dev/null +++ b/config/sidekiq.yml @@ -0,0 +1,6 @@ +:queues: + - default + - searchkick +:verbose: false +:concurrency: 10 +:timeout: 60 \ No newline at end of file diff --git a/db/schema.rb b/db/schema.rb index 9b66af81..e27e4efe 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2022_08_01_155837) do +ActiveRecord::Schema.define(version: 2022_07_28_131300) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" @@ -49,9 +49,11 @@ t.string "who" t.string "provider" t.bigint "user_id" + t.bigint "{}_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" + t.index ["{}_id"], name: "index_ecds_rails_auth_engine_logins_on_{}_id" end create_table "ecds_rails_auth_engine_tokens", force: :cascade do |t| diff --git a/entrypoint.sh b/entrypoint.sh index 71261527..06eaf4cc 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,4 +1,4 @@ - +#!/bin/bash /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 +/usr/local/bin/bundle exec sidekiq& +bundle exec puma -C config/puma.rb diff --git a/lib/snippets.rb b/lib/snippets.rb index 4cef4160..f1dbc534 100644 --- a/lib/snippets.rb +++ b/lib/snippets.rb @@ -155,7 +155,7 @@ # next unless m.file.attached? - next unless File.exists? ActiveStorage::Blob.service.send(:path_for, m.file.key) + next unless File.exist? 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)), @@ -172,7 +172,7 @@ m = MapIcon.find(id) next unless m.file.attached? - next unless File.exists? ActiveStorage::Blob.service.send(:path_for, m.file.key) + next unless File.exist? ActiveStorage::Blob.service.send(:path_for, m.file.key) m.file.attach( io: File.open(ActiveStorage::Blob.service.send(:path_for, m.file.key)), @@ -270,4 +270,12 @@ sites.each do |ts| Apartment::Tenant.switch! ts Tour.all.each { |t| t.update(restrict_bounds: false) } +end + +no_medium = [] +tenants.each do |t| + Apartment::Tenant.switch!(t) + TourMedium.all.each do |tm| + no_medium.push(tm) if tm.medium.nil? + end end \ No newline at end of file diff --git a/screenshot.tiff b/screenshot.tiff new file mode 100644 index 00000000..81b2974c Binary files /dev/null and b/screenshot.tiff differ diff --git a/spec/controllers/v3/media_controller_spec.rb b/spec/controllers/v3/media_controller_spec.rb index 0e2116af..7d8be9a5 100644 --- a/spec/controllers/v3/media_controller_spec.rb +++ b/spec/controllers/v3/media_controller_spec.rb @@ -88,7 +88,7 @@ it 'returns the medium when unpublished but requested is authorized' do medium = create(:medium) - medium.save + # medium.save expect(medium.file.attached?).to be true user = create(:user) user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) diff --git a/spec/controllers/v3/tours_controller_spec.rb b/spec/controllers/v3/tours_controller_spec.rb index 635f7cca..8dc97db5 100644 --- a/spec/controllers/v3/tours_controller_spec.rb +++ b/spec/controllers/v3/tours_controller_spec.rb @@ -21,7 +21,7 @@ expect(json.count).to eq(Tour.published.count) end - it 'returns a 200 response when requeted by slug' do + it 'returns a 200 response when requested by slug' do tour = create(:tour) tour.update(published: true) get :index, params: { tenant: tour.tenant, slug: tour.slug } @@ -79,7 +79,7 @@ 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] + 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) @@ -115,6 +115,7 @@ 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)) + tour.save user = create(:user) user.tour_sets = [] user.tours << tour @@ -148,12 +149,12 @@ describe 'POST #create' do context 'with valid params' do - it 'return 401 when unauthenciated' do + it 'return 401 when unauthenticated' do post :create, params: { data: { type: 'tours', attributes: { title: 'Burrito Tour' } }, tenant: TourSet.first.subdir } expect(response.status).to eq(401) end - it 'return 401 when authenciated but not an admin for current tenant' do + it 'return 401 when authenticated but not an admin for current tenant' do user = create(:user) user.update(super: false) user.tour_sets = [] @@ -162,7 +163,7 @@ expect(response.status).to eq(401) end - it 'return 201 when authenciated but an admin for current tenant' do + it 'return 201 when authenticated but an admin for current tenant' do user = create(:user) user.update(super: false) user.tour_sets << TourSet.find_by(subdir: Apartment::Tenant.current) @@ -173,7 +174,7 @@ expect(Tour.count).to eq(original_tour_count + 1) end - it 'return 201 when authenciated by super' do + it 'return 201 when authenticated by super' do user = create(:user) user.tour_sets = [] user.update(super: true) @@ -210,13 +211,13 @@ describe 'PUT #update' do context 'with valid params' do - it 'return 401 when unauthenciated' do + it 'return 401 when unauthenticated' 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 'return 401 when authenciated but not an admin for current tenant' do + it 'return 401 when authenticated but not an admin for current tenant' do tour = create(:tour, published: false) user = create(:user) user.update(super: false) @@ -226,7 +227,7 @@ expect(response.status).to eq(401) end - it 'return 200 and updated tour when authenciated but an admin for current tenant' do + it 'returns 200 and updated tour when authenticated but an admin for current tenant' do tour = create(:tour, published: false) user = create(:user) user.update(super: false) @@ -240,7 +241,7 @@ expect(Tour.find(tour.id).title).to eq(new_title) end - it 'return 200 and updated tour when authenciated by super' do + it 'return 200 and updated tour when authenticated by super' do tour = create(:tour, published: false) user = create(:user) user.tour_sets = [] @@ -254,7 +255,7 @@ expect(Tour.find(tour.id).title).to eq(new_title) end - it 'return 200 and updated tour when authenciated by tour author' do + it 'return 200 and updated tour when authenticated by tour author' do tour = create(:tour, published: false) user = create(:user) user.update(super: false) @@ -342,13 +343,13 @@ end describe 'DELETE #destroy' do - it 'return 401 when unauthenciated' do + it 'return 401 when unauthenticated' 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 + it 'return 401 when authenticated but not an admin for current tenant' do tour = create(:tour, published: false) user = create(:user) user.update(super: false) @@ -358,7 +359,7 @@ expect(response.status).to eq(401) end - it 'return 204 and one less tour when authenciated but an admin for current tenant' do + it 'return 204 and one less tour when authenticated but an admin for current tenant' do tour = create(:tour, published: false) user = create(:user) user.update(super: false) @@ -370,7 +371,7 @@ expect(Tour.count).to eq(tour_count - 1) end - it 'return 204 and one less tour when authenciated by super' do + it 'return 204 and one less tour when authenticated by super' do tour = create(:tour, published: false) user = create(:user) user.tour_sets = [] @@ -382,7 +383,7 @@ expect(Tour.count).to eq(tour_count - 1) end - it 'return 204 and one less tour when authenciated by tour author' do + it 'return 204 and one less tour when authenticated by tour author' do tour = create(:tour, published: false) user = create(:user) user.update(super: false) diff --git a/spec/controllers/v3/users_controller_spec.rb b/spec/controllers/v3/users_controller_spec.rb index 872f7425..110232af 100644 --- a/spec/controllers/v3/users_controller_spec.rb +++ b/spec/controllers/v3/users_controller_spec.rb @@ -154,7 +154,6 @@ 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 @@ -167,7 +166,6 @@ 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) @@ -181,7 +179,6 @@ 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) @@ -202,14 +199,13 @@ 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) + expect(initial_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) @@ -222,7 +218,6 @@ 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) diff --git a/spec/controllers/v4/admin/crud_controller_spec.rb b/spec/controllers/v4/admin/crud_controller_spec.rb new file mode 100644 index 00000000..32bcbafd --- /dev/null +++ b/spec/controllers/v4/admin/crud_controller_spec.rb @@ -0,0 +1,53 @@ +require "rails_helper" + +RSpec.describe V4::Admin::CrudController, type: :controller do + describe "PUT #create" do + it "returns 401 when unauthenticated" do + post :create, params: { tenant: TourSet.last.subdir, data: { model: "tour", title: Faker::Book.title } } + expect(response.status).to eq(401) + end + end + + describe "put #update" do + it "returns 401 when unauthenticated" do + post :create, params: { tenant: TourSet.last.subdir, data: { model: "tour", title: Faker::Book.title } } + expect(response.status).to eq(401) + end + + it "returns 204 and updates tour title when authenticated" 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 + request_body = { + model: 'tour', + attribute: "title", + value: new_title + } + expect(Tour.find(tour.id).title).not_to eq(new_title) + put :update, params: { id: tour.id, **request_body, tenant: Apartment::Tenant.current } + expect(response.status).to eq(204) + expect(Tour.find(tour.id).title).to eq(new_title) + end + + it "adds a belongs to association" do + tour = create(:tour) + new_theme = create(:theme) + user = create(:user, super: true) + signed_cookie(user) + expect(tour.theme).not_to eq(new_theme) + tour.update(theme: new_theme) + request_body = { + model: "tour", + attribute: "theme", + value: new_theme.id.to_s, + related_model: "theme", + relation_type: "belongs_to" + } + put :update, params: { id: tour.id, **request_body, tenant: Apartment::Tenant.current } + expect(tour.theme).to eq(new_theme) + end + end +end diff --git a/spec/controllers/v4/tours_controller_spec.rb b/spec/controllers/v4/tours_controller_spec.rb new file mode 100644 index 00000000..8bc2e0f6 --- /dev/null +++ b/spec/controllers/v4/tours_controller_spec.rb @@ -0,0 +1,98 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe V4::Public::ToursController, type: :controller do + before(:each) { Tour.reindex } + + 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 } + Tour.reindex + get :index, params: { tenant: 'public' } + expect(json).to be_empty + expect(response.status).to eq(200) + end + + it 'returns a 200 response' do + tour = create(:tour) + Tour.reindex + get :index, params: { tenant: tour.tenant } + expect(response.status).to eq(200) + expect(json.count).to eq(Tour.published.count) + end + + it 'returns a 200 response when requested by slug' do + tour = create(:tour) + tour.update(published: true) + Tour.reindex + get :index, params: { tenant: tour.tenant, slug: tour.slug } + expect(response.status).to eq(200) + expect(json[:title]).to eq(tour.title) + 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) + Tour.reindex + get :index, params: { tenant: tour.tenant, slug: tour.slug } + expect(response.status).to eq(200) + expect(json[:title]).not_to eq(tour.title) + expect(json[: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) + Tour.reindex + get :index, params: { tenant: tour.tenant, slug: tour.slug } + expect(response.status).to eq(200) + expect(json[: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) + Tour.reindex + get :index, params: { tenant: tour.tenant, slug: tour.slug } + expect(response.status).to eq(200) + expect(json[: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) + Tour.reindex + 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) + Tour.reindex + 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 +end \ No newline at end of file diff --git a/spec/factories/map_icons.rb b/spec/factories/map_icons.rb index f65c076c..bd363701 100755 --- a/spec/factories/map_icons.rb +++ b/spec/factories/map_icons.rb @@ -5,6 +5,5 @@ 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/media.rb b/spec/factories/media.rb index f85290f5..5995df20 100644 --- a/spec/factories/media.rb +++ b/spec/factories/media.rb @@ -7,7 +7,6 @@ caption { Faker::TvShows::RickAndMorty.quote } 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 diff --git a/spec/factories/stops.rb b/spec/factories/stops.rb index 677a4713..5219e51f 100644 --- a/spec/factories/stops.rb +++ b/spec/factories/stops.rb @@ -10,7 +10,6 @@ description { Faker::Hipster.paragraph(sentence_count: 2, supplemental: true, random_sentences_to_add: 4) } lat { Faker::Address.latitude } lng { Faker::Address.longitude } - created_at { Faker::Number.number(digits: 10) } factory :stop_with_media do transient do diff --git a/spec/factories/themes.rb b/spec/factories/themes.rb index c7b4720b..6172e742 100644 --- a/spec/factories/themes.rb +++ b/spec/factories/themes.rb @@ -4,6 +4,5 @@ FactoryBot.define do factory :theme do title { Faker::Color.name } - created_at { Faker::Number.number(digits: 10) } end end diff --git a/spec/jobs/reindex_job_spec.rb b/spec/jobs/reindex_job_spec.rb new file mode 100644 index 00000000..417a630b --- /dev/null +++ b/spec/jobs/reindex_job_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe ReindexJob, type: :job do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/medium_spec.rb b/spec/models/medium_spec.rb index e6df5afb..e26d3413 100644 --- a/spec/models/medium_spec.rb +++ b/spec/models/medium_spec.rb @@ -13,7 +13,7 @@ expect(medium.file.attached?).to be true end - it 'gets image from youtube when downloaded image is a StrinIO object and sets embed' do + it 'gets image from youtube when downloaded image is a StringIO 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) @@ -61,7 +61,6 @@ 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') @@ -82,17 +81,27 @@ end - context 'createing images' do + context 'creating images' do + it "creates a medium record with attachment" do + poo = nil + File.open(Rails.root.join('spec', 'factories', 'images', 'atl_base64.txt'), "r") do |b64| + poo = b64.read + end + Medium.create(base_sixty_four: poo, filename: "atl.png") + medium = Medium.find_by(filename: "atl.png") + expect(medium.filename).to eq("atl.png") + end + 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 = Medium.find(medium.id) medium.save - expect(medium.lqip_width).not_to be nil + expect(medium).not_to be nil + # expect(medium.lqip_width).not_to be nil end it 'saves a gif' do diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 8484bcbb..3bb89889 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -38,7 +38,7 @@ RSpec.configure do |config| # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures - config.fixture_path = "#{::Rails.root}/spec/fixtures" + config.fixture_paths = [ "#{::Rails.root}/spec/fixtures" ] # 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 @@ -75,16 +75,16 @@ end config.before(:each) do - MiniMagick.configure do |config| - config.validate_on_create = false - end + # 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' + ActiveStorage::Current.url_options = { host: 'http://test.host' } # Switch to the below version for Rails 7 # ActiveStorage::Current.url_options = { host: 'http://test.host' } # host! 'atlanta.lvh.me' diff --git a/spec/requests/tours_request_spec.rb b/spec/requests/v3/tours_request_spec.rb similarity index 96% rename from spec/requests/tours_request_spec.rb rename to spec/requests/v3/tours_request_spec.rb index a0b877c0..d2291428 100755 --- a/spec/requests/tours_request_spec.rb +++ b/spec/requests/v3/tours_request_spec.rb @@ -4,6 +4,7 @@ RSpec.describe 'V3::Tours', type: :request do describe 'GET /:tenant/tours' do + Tour.reindex before { get "/#{Apartment::Tenant.current}/tours", headers: { 'HTTP_USER_AGENT': 'bot' } } diff --git a/spec/requests/v4/v4_stops_request_spec.rb b/spec/requests/v4/v4_stops_request_spec.rb new file mode 100755 index 00000000..cd323792 --- /dev/null +++ b/spec/requests/v4/v4_stops_request_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'V4::Stops', type: :request do + describe 'GET /:tenant/v4/public/stops' do + before { + Apartment::Tenant.switch! TourSet.last.subdir + Stop.reindex + get "/#{Apartment::Tenant.current}/v4/public/stops", headers: { 'HTTP_USER_AGENT': 'bot' } + } + + it 'returns only published stops' do + expect(json.size).to eq(Stop.all.filter(&:published).count) + end + + it 'returns status code 200' do + expect(response).to have_http_status(200) + end + end +end diff --git a/spec/requests/v4/v4_tour_sets_request_spec.rb b/spec/requests/v4/v4_tour_sets_request_spec.rb new file mode 100755 index 00000000..9ad27ac1 --- /dev/null +++ b/spec/requests/v4/v4_tour_sets_request_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'V4::TourSets', type: :request do + describe 'GET /:tenant/v4/public/tours' do + before { + Apartment::Tenant.switch! 'public' + TourSet.reindex + get "/public/v4/public/tour-sets", headers: { 'HTTP_USER_AGENT': 'bot' } + } + + it 'returns only published tours' do + Apartment::Tenant.switch! 'public' + expect(json.size).to eq(TourSet.all.filter { |ts| ts.published_tours.count > 0 }.count) + end + + it 'returns status code 200' do + expect(response).to have_http_status(200) + end + end +end diff --git a/spec/requests/v4/v4_tours_request_spec.rb b/spec/requests/v4/v4_tours_request_spec.rb new file mode 100755 index 00000000..511d3b87 --- /dev/null +++ b/spec/requests/v4/v4_tours_request_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'V4::Tours', type: :request do + describe 'GET /:tenant/v4/public/tours' do + before { + Apartment::Tenant.switch! TourSet.last.subdir + Tour.reindex + get "/#{Apartment::Tenant.current}/v4/public/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/spec_helper.rb b/spec/spec_helper.rb index 77c71181..5a4b8cb9 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -3,42 +3,13 @@ # if ENV['COV'] == 'simple' # require 'simplecov' # else -# require 'coveralls' -# Coveralls.wear! # end -# require 'simplecov' -# SimpleCov.start - -require 'coveralls' -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') +# WebMock.disable_net_connect!(allow: '45.33.24.119') +# WebMock.disable_net_connect!(allow: 'localhost:9200') +WebMock.disable_net_connect!(allow: ['localhost', 'localhost:9200', '45.33.24.119', '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`.