diff --git a/.env.example b/.env.example index 329465ffd..7e8087350 100644 --- a/.env.example +++ b/.env.example @@ -12,6 +12,11 @@ DATABASE_USER=postgres DATABASE_NAME=timeoverflow_development +# Rails settings +RAILS_LOG_LEVEL=debug +STORAGE_PROVIDER=amazon +FORCE_SSL=true + # Host part of the url for mail links: MAIL_LINK_HOST=localhost:3000 MAIL_LINK_PROTO=http @@ -26,7 +31,7 @@ SMTP_PORT=587 # List of emails for superadmin users ADMINS="admin@timeoverflow.org" -# AWS settings +# AWS settings (if STORAGE_PROVIDER=amazon) AWS_ACCESS_KEY_ID=XXXXXXXX AWS_SECRET_ACCESS_KEY=XXXXXXXX AWS_BUCKET=timeoverflow_development diff --git a/.github/workflows/docker_build.yml b/.github/workflows/docker_build.yml new file mode 100644 index 000000000..ad058acc6 --- /dev/null +++ b/.github/workflows/docker_build.yml @@ -0,0 +1,16 @@ +name: Docker Build + +on: [push, pull_request] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Build docker + run: docker build . + - name: Test docker compose + run: docker-compose up -d + - run: sleep 15 # wait for the server to start + - name: Check server is up + run: curl -s http://localhost:3000 diff --git a/.gitignore b/.gitignore index 85dbc60e4..5c942096a 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ tags .sass-cache capybara-*.html /vendor/bundle +/public/assets /storage/ /coverage/ /spec/tmp/* diff --git a/.ruby-version b/.ruby-version index ec1cf33c3..be94e6f53 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.6.3 +3.2.2 diff --git a/Capfile b/Capfile deleted file mode 100644 index 0f81c1684..000000000 --- a/Capfile +++ /dev/null @@ -1,22 +0,0 @@ -# Load DSL and set up stages -require 'capistrano/setup' - -# Include default deployment tasks -require 'capistrano/deploy' - -# Include tasks from other gems included in your Gemfile -# -# For documentation on these, see for example: -# -# https://github.com/capistrano/rvm -# https://github.com/capistrano/rbenv -# https://github.com/capistrano/chruby -# https://github.com/capistrano/bundler -# https://github.com/capistrano/rails -# https://github.com/capistrano/passenger -# -require 'capistrano/rails' -require 'capistrano/rbenv' - -# Load custom tasks from `lib/capistrano/tasks` if you have any defined -Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r } diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..933db8346 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,100 @@ +FROM ruby:3.2 AS builder + +RUN apt-get update && apt-get upgrade -y && apt-get install -y ca-certificates curl gnupg && \ + curl -fsSL https://deb.nodesource.com/setup_21.x | bash - && \ + apt-get install -y \ + build-essential \ + nodejs \ + postgresql-client \ + libpq-dev && \ + apt-get clean + +# throw errors if Gemfile has been modified since Gemfile.lock +RUN bundle config --global frozen 1 + +WORKDIR /app + +# Copy package dependencies files only to ensure maximum cache hit +COPY ./Gemfile /app/Gemfile +COPY ./Gemfile.lock /app/Gemfile.lock + +RUN gem install bundler:$(grep -A 1 'BUNDLED WITH' Gemfile.lock | tail -n 1 | xargs) && \ + bundle config --local without 'development test' && \ + bundle install -j4 --retry 3 && \ + # Remove unneeded gems + bundle clean --force && \ + # Remove unneeded files from installed gems (cache, *.o, *.c) + rm -rf /usr/local/bundle/cache && \ + find /usr/local/bundle/ -name "*.c" -delete && \ + find /usr/local/bundle/ -name "*.o" -delete && \ + find /usr/local/bundle/ -name ".git" -exec rm -rf {} + && \ + find /usr/local/bundle/ -name ".github" -exec rm -rf {} + && \ + find /usr/local/bundle/ -name "spec" -exec rm -rf {} + + +# copy the rest of files +COPY ./app /app/app +COPY ./bin /app/bin +COPY ./config /app/config +COPY ./db /app/db +COPY ./lib /app/lib +COPY ./public/*.* /app/public/ +COPY ./config.ru /app/config.ru +COPY ./Rakefile /app/Rakefile + +# Compile assets +# +# For an app using encrypted credentials, Rails raises a `MissingKeyError` +# if the master key is missing. Because on CI there is no master key, +# we hide the credentials while compiling assets (by renaming them before and after) +# +RUN mv config/credentials.yml.enc config/credentials.yml.enc.bak 2>/dev/null || true +RUN mv config/credentials config/credentials.bak 2>/dev/null || true + +RUN RAILS_ENV=production \ + SECRET_KEY_BASE=dummy \ + RAILS_MASTER_KEY=dummy \ + DB_ADAPTER=nulldb \ + bundle exec rails assets:precompile + +RUN mv config/credentials.yml.enc.bak config/credentials.yml.enc 2>/dev/null || true +RUN mv config/credentials.bak config/credentials 2>/dev/null || true + +RUN rm -rf tmp/cache vendor/bundle test spec .git + +# This image is for production env only +FROM ruby:3.2-slim AS final + +RUN apt-get update && \ + apt-get install -y postgresql-client \ + imagemagick \ + libvips \ + curl \ + supervisor && \ + apt-get clean + +EXPOSE 3000 + +ENV RAILS_LOG_TO_STDOUT true +ENV RAILS_SERVE_STATIC_FILES true +ENV RAILS_ENV production + +ARG RUN_RAILS +ARG RUN_SIDEKIQ + +# Add user +RUN addgroup --system --gid 1000 app && \ + adduser --system --uid 1000 --home /app --group app + +WORKDIR /app +COPY ./entrypoint.sh /app/entrypoint.sh +COPY ./supervisord.conf /etc/supervisord.conf +COPY --from=builder --chown=app:app /usr/local/bundle/ /usr/local/bundle/ +COPY --from=builder --chown=app:app /app /app + +USER app +HEALTHCHECK --interval=1m --timeout=5s --start-period=10s \ + CMD (curl -IfSs http://localhost:3000/ -A "HealthCheck: Docker/1.0") || exit 1 + + +ENTRYPOINT ["/app/entrypoint.sh"] +CMD ["/usr/bin/supervisord"] \ No newline at end of file diff --git a/Gemfile b/Gemfile index ff1ac20da..bb944a6b1 100644 --- a/Gemfile +++ b/Gemfile @@ -1,11 +1,12 @@ source 'https://rubygems.org' -gem 'rails', '~> 6.1.1' +gem 'rails', '~> 7.0.8' gem 'rails-i18n', '~> 7.0' +gem 'puma', '~> 6.4' gem 'rdiscount', '~> 2.2.7' gem 'rubyzip', '~> 2.3.0' gem 'activeadmin', '~> 2.14' -gem 'bootsnap', '~> 1.12.0', require: false +gem 'bootsnap', '~> 1.12', require: false gem 'has_scope', '~> 0.7.2' gem 'pundit', '~> 2.1.0' gem 'pg', '~> 1.4' @@ -13,14 +14,11 @@ gem 'json_translate', '~> 4.0.0' gem 'devise', '~> 4.9.1' gem 'devise-i18n', '~> 1.11.0' gem 'http_accept_language', '~> 2.1.1' -gem 'unicorn', '~> 5.5.1' gem 'kaminari', '~> 1.2.1' gem 'simple_form', '~> 5.0.2' -gem 'rollbar', '~> 2.22.1' -gem 'prawn', '~> 2.4.0' +gem 'prawn', '~> 2.5.0' gem 'prawn-table', '~> 0.2.2' gem 'pg_search', '~> 2.3.5' -gem 'skylight', '~> 5.0' gem 'sidekiq', '~> 6.5' gem 'sidekiq-cron', '~> 1.9.1' gem 'aws-sdk-s3', '~> 1.94', require: false @@ -31,17 +29,17 @@ gem 'active_storage_validations', '~> 1.1.3' gem 'jquery-rails', '~> 4.4.0' gem 'bootstrap-sass', '~> 3.4' gem 'sassc-rails', '~> 2.1.2' -gem 'uglifier', '~> 4.2.0' gem 'select2-rails', '~> 4.0.13' +group :production do + # we are using an ExecJS runtime only on the precompilation phase + gem "uglifier", "~> 4.2.0", require: false +end + group :development do - gem 'listen', '~> 3.2.0' gem 'localeapp', '~> 3.3', require: false gem 'letter_opener', '~> 1.7.0' gem 'web-console', '~> 4.1.0' - gem 'capistrano', '~> 3.1' - gem 'capistrano-rails', '~> 1.1' - gem 'capistrano-rbenv', '~> 2.1' end group :development, :test do @@ -52,14 +50,13 @@ group :development, :test do end group :test do - gem 'rspec-rails', '~> 4.0.0' + gem 'rspec-rails', '~> 6.0' gem 'rails-controller-testing' - gem 'database_cleaner', '~> 1.8.5' + gem 'database_cleaner', '~> 2.0' gem 'shoulda-matchers', '~> 4.4' gem 'fabrication', '~> 2.20' gem 'faker', '~> 2.15' gem 'capybara', '~> 3.29' - gem 'selenium-webdriver', '~> 4.1.0' - gem 'webdrivers', '~> 5.3' + gem 'selenium-webdriver', '~> 4.16' gem 'simplecov', '~> 0.22', require: false end diff --git a/Gemfile.lock b/Gemfile.lock index 7aaf022bb..dfe6a6612 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,45 +1,52 @@ GEM remote: https://rubygems.org/ specs: - actioncable (6.1.7.6) - actionpack (= 6.1.7.6) - activesupport (= 6.1.7.6) + actioncable (7.0.8.1) + actionpack (= 7.0.8.1) + activesupport (= 7.0.8.1) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.1.7.6) - actionpack (= 6.1.7.6) - activejob (= 6.1.7.6) - activerecord (= 6.1.7.6) - activestorage (= 6.1.7.6) - activesupport (= 6.1.7.6) + actionmailbox (7.0.8.1) + actionpack (= 7.0.8.1) + activejob (= 7.0.8.1) + activerecord (= 7.0.8.1) + activestorage (= 7.0.8.1) + activesupport (= 7.0.8.1) mail (>= 2.7.1) - actionmailer (6.1.7.6) - actionpack (= 6.1.7.6) - actionview (= 6.1.7.6) - activejob (= 6.1.7.6) - activesupport (= 6.1.7.6) + net-imap + net-pop + net-smtp + actionmailer (7.0.8.1) + actionpack (= 7.0.8.1) + actionview (= 7.0.8.1) + activejob (= 7.0.8.1) + activesupport (= 7.0.8.1) mail (~> 2.5, >= 2.5.4) + net-imap + net-pop + net-smtp rails-dom-testing (~> 2.0) - actionpack (6.1.7.6) - actionview (= 6.1.7.6) - activesupport (= 6.1.7.6) - rack (~> 2.0, >= 2.0.9) + actionpack (7.0.8.1) + actionview (= 7.0.8.1) + activesupport (= 7.0.8.1) + rack (~> 2.0, >= 2.2.4) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.1.7.6) - actionpack (= 6.1.7.6) - activerecord (= 6.1.7.6) - activestorage (= 6.1.7.6) - activesupport (= 6.1.7.6) + actiontext (7.0.8.1) + actionpack (= 7.0.8.1) + activerecord (= 7.0.8.1) + activestorage (= 7.0.8.1) + activesupport (= 7.0.8.1) + globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (6.1.7.6) - activesupport (= 6.1.7.6) + actionview (7.0.8.1) + activesupport (= 7.0.8.1) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) - active_storage_validations (1.1.3) + active_storage_validations (1.1.4) activejob (>= 5.2.0) activemodel (>= 5.2.0) activestorage (>= 5.2.0) @@ -53,102 +60,91 @@ GEM kaminari (~> 1.0, >= 1.2.1) railties (>= 6.1, < 7.1) ransack (>= 2.1.1, < 4) - activejob (6.1.7.6) - activesupport (= 6.1.7.6) + activejob (7.0.8.1) + activesupport (= 7.0.8.1) globalid (>= 0.3.6) - activemodel (6.1.7.6) - activesupport (= 6.1.7.6) - activerecord (6.1.7.6) - activemodel (= 6.1.7.6) - activesupport (= 6.1.7.6) - activestorage (6.1.7.6) - actionpack (= 6.1.7.6) - activejob (= 6.1.7.6) - activerecord (= 6.1.7.6) - activesupport (= 6.1.7.6) + activemodel (7.0.8.1) + activesupport (= 7.0.8.1) + activerecord (7.0.8.1) + activemodel (= 7.0.8.1) + activesupport (= 7.0.8.1) + activestorage (7.0.8.1) + actionpack (= 7.0.8.1) + activejob (= 7.0.8.1) + activerecord (= 7.0.8.1) + activesupport (= 7.0.8.1) marcel (~> 1.0) mini_mime (>= 1.1.0) - activesupport (6.1.7.6) + activesupport (7.0.8.1) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) - zeitwerk (~> 2.3) - addressable (2.8.5) + addressable (2.8.6) public_suffix (>= 2.0.2, < 6.0) - airbrussh (1.4.0) - sshkit (>= 1.6.1, != 1.7.0) - arbre (1.5.0) - activesupport (>= 3.0.0, < 7.1) - ruby2_keywords (>= 0.0.2, < 1.0) + arbre (1.7.0) + activesupport (>= 3.0.0) + ruby2_keywords (>= 0.0.2) ast (2.4.2) - autoprefixer-rails (10.4.13.0) + autoprefixer-rails (10.4.16.0) execjs (~> 2) - aws-eventstream (1.1.1) - aws-partitions (1.451.0) - aws-sdk-core (3.114.0) - aws-eventstream (~> 1, >= 1.0.2) - aws-partitions (~> 1, >= 1.239.0) - aws-sigv4 (~> 1.1) - jmespath (~> 1.0) - aws-sdk-kms (1.43.0) - aws-sdk-core (~> 3, >= 3.112.0) + aws-eventstream (1.3.0) + aws-partitions (1.887.0) + aws-sdk-core (3.191.0) + aws-eventstream (~> 1, >= 1.3.0) + aws-partitions (~> 1, >= 1.651.0) + aws-sigv4 (~> 1.8) + jmespath (~> 1, >= 1.6.1) + aws-sdk-kms (1.77.0) + aws-sdk-core (~> 3, >= 3.191.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.94.1) - aws-sdk-core (~> 3, >= 3.112.0) + aws-sdk-s3 (1.143.0) + aws-sdk-core (~> 3, >= 3.191.0) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.1) - aws-sigv4 (1.2.3) + aws-sigv4 (~> 1.8) + aws-sigv4 (1.8.0) aws-eventstream (~> 1, >= 1.0.2) - bcrypt (3.1.18) + base64 (0.2.0) + bcrypt (3.1.20) + bigdecimal (3.1.7) bindex (0.8.1) - bootsnap (1.12.0) + bootsnap (1.18.3) msgpack (~> 1.2) bootstrap-sass (3.4.1) autoprefixer-rails (>= 5.2.1) sassc (>= 2.0.0) builder (3.2.4) byebug (11.1.3) - capistrano (3.15.0) - airbrussh (>= 1.0.0) - i18n - rake (>= 10.0.0) - sshkit (>= 1.9.0) - capistrano-bundler (2.0.1) - capistrano (~> 3.1) - capistrano-rails (1.6.1) - capistrano (~> 3.1) - capistrano-bundler (>= 1.1, < 3) - capistrano-rbenv (2.2.0) - capistrano (~> 3.1) - sshkit (~> 1.3) - capybara (3.36.0) + capybara (3.40.0) addressable matrix mini_mime (>= 0.1.3) - nokogiri (~> 1.8) + nokogiri (~> 1.11) rack (>= 1.6.0) rack-test (>= 0.6.3) regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) - childprocess (4.1.0) - concurrent-ruby (1.2.2) + concurrent-ruby (1.2.3) connection_pool (2.4.1) crass (1.0.6) - database_cleaner (1.8.5) + database_cleaner (2.0.2) + database_cleaner-active_record (>= 2, < 3) + database_cleaner-active_record (2.1.0) + activerecord (>= 5.a) + database_cleaner-core (~> 2.0.0) + database_cleaner-core (2.0.1) date (3.3.4) - devise (4.9.1) + devise (4.9.3) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 4.1.0) responders warden (~> 1.2.3) - devise-i18n (1.11.0) + devise-i18n (1.11.1) devise (>= 4.9.0) - diff-lcs (1.4.4) + diff-lcs (1.5.1) docile (1.4.0) - domain_name (0.5.20190701) - unf (>= 0.0.5, < 1.0.0) + domain_name (0.6.20240107) dotenv (2.7.6) dotenv-rails (2.7.6) dotenv (= 2.7.6) @@ -156,18 +152,18 @@ GEM erubi (1.12.0) et-orbi (1.2.7) tzinfo - execjs (2.8.1) - fabrication (2.22.0) - faker (2.17.0) - i18n (>= 1.6, < 2) - ffi (1.15.5) + execjs (2.9.1) + fabrication (2.31.0) + faker (2.23.0) + i18n (>= 1.8.11, < 2) + ffi (1.16.3) formtastic (4.0.0) actionpack (>= 5.2.0) formtastic_i18n (0.7.0) fugit (1.9.0) et-orbi (~> 1, >= 1.2.7) raabro (~> 1.4) - gli (2.21.0) + gli (2.21.1) globalid (1.2.1) activesupport (>= 6.1) has_scope (0.7.2) @@ -182,17 +178,17 @@ GEM image_processing (1.12.2) mini_magick (>= 4.9.5, < 5) ruby-vips (>= 2.0.17, < 3) - inherited_resources (1.13.1) - actionpack (>= 5.2, < 7.1) - has_scope (~> 0.6) - railties (>= 5.2, < 7.1) - responders (>= 2, < 4) - jmespath (1.6.1) + inherited_resources (1.14.0) + actionpack (>= 6.0) + has_scope (>= 0.6) + railties (>= 6.0) + responders (>= 2) + jmespath (1.6.2) jquery-rails (4.4.0) rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) - json (2.6.3) + json (2.7.1) json_translate (4.0.1) activerecord (>= 4.2.0) kaminari (1.2.2) @@ -207,14 +203,11 @@ GEM activerecord kaminari-core (= 1.2.2) kaminari-core (1.2.2) - kgio (2.11.3) - launchy (2.5.0) - addressable (~> 2.7) + language_server-protocol (3.17.0.3) + launchy (2.5.2) + addressable (~> 2.8) letter_opener (1.7.0) launchy (~> 2.2) - listen (3.2.1) - rb-fsevent (~> 0.10, >= 0.10.3) - rb-inotify (~> 0.9, >= 0.9.10) localeapp (3.3.0) gli i18n (>= 0.7, < 2) @@ -231,68 +224,68 @@ GEM marcel (1.0.2) matrix (0.4.2) method_source (1.0.0) - mime-types (3.4.1) + mime-types (3.5.2) mime-types-data (~> 3.2015) - mime-types-data (3.2022.0105) - mini_magick (4.11.0) + mime-types-data (3.2023.1205) + mini_magick (4.12.0) mini_mime (1.1.5) - mini_portile2 (2.8.5) - minitest (5.20.0) - msgpack (1.5.2) - net-imap (0.3.7) + minitest (5.22.2) + msgpack (1.7.2) + net-imap (0.4.10) date net-protocol net-pop (0.1.2) net-protocol net-protocol (0.2.2) timeout - net-scp (3.0.0) - net-ssh (>= 2.6.5, < 7.0.0) - net-smtp (0.4.0) + net-smtp (0.4.0.1) net-protocol - net-ssh (6.1.0) netrc (0.11.0) - nio4r (2.6.0) - nokogiri (1.13.10) - mini_portile2 (~> 2.8.0) + nio4r (2.7.0) + nokogiri (1.16.2-arm64-darwin) + racc (~> 1.4) + nokogiri (1.16.2-x86_64-linux) racc (~> 1.4) orm_adapter (0.5.0) - parallel (1.20.1) - parser (3.0.1.1) + parallel (1.24.0) + parser (3.3.0.5) ast (~> 2.4.1) - pdf-core (0.9.0) - pg (1.4.6) - pg_search (2.3.5) + racc + pdf-core (0.10.0) + pg (1.5.4) + pg_search (2.3.6) activerecord (>= 5.2) activesupport (>= 5.2) - prawn (2.4.0) - pdf-core (~> 0.9.0) - ttfunk (~> 1.7) + prawn (2.5.0) + matrix (~> 0.4) + pdf-core (~> 0.10.0) + ttfunk (~> 1.8) prawn-table (0.2.2) prawn (>= 1.3.0, < 3.0.0) public_suffix (5.0.4) - pundit (2.1.0) + puma (6.4.2) + nio4r (~> 2.0) + pundit (2.1.1) activesupport (>= 3.0.0) raabro (1.4.0) racc (1.7.3) - rack (2.2.8) + rack (2.2.8.1) rack-test (2.1.0) rack (>= 1.3) - rails (6.1.7.6) - actioncable (= 6.1.7.6) - actionmailbox (= 6.1.7.6) - actionmailer (= 6.1.7.6) - actionpack (= 6.1.7.6) - actiontext (= 6.1.7.6) - actionview (= 6.1.7.6) - activejob (= 6.1.7.6) - activemodel (= 6.1.7.6) - activerecord (= 6.1.7.6) - activestorage (= 6.1.7.6) - activesupport (= 6.1.7.6) + rails (7.0.8.1) + actioncable (= 7.0.8.1) + actionmailbox (= 7.0.8.1) + actionmailer (= 7.0.8.1) + actionpack (= 7.0.8.1) + actiontext (= 7.0.8.1) + actionview (= 7.0.8.1) + activejob (= 7.0.8.1) + activemodel (= 7.0.8.1) + activerecord (= 7.0.8.1) + activestorage (= 7.0.8.1) + activesupport (= 7.0.8.1) bundler (>= 1.15.0) - railties (= 6.1.7.6) - sprockets-rails (>= 2.0.0) + railties (= 7.0.8.1) rails-controller-testing (1.0.5) actionpack (>= 5.0.1.rc1) actionview (>= 5.0.1.rc1) @@ -301,30 +294,28 @@ GEM activesupport (>= 5.0.0) minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.5.0) - loofah (~> 2.19, >= 2.19.1) + rails-html-sanitizer (1.6.0) + loofah (~> 2.21) + nokogiri (~> 1.14) rails-i18n (7.0.8) i18n (>= 0.7, < 2) railties (>= 6.0.0, < 8) - railties (6.1.7.6) - actionpack (= 6.1.7.6) - activesupport (= 6.1.7.6) + railties (7.0.8.1) + actionpack (= 7.0.8.1) + activesupport (= 7.0.8.1) method_source rake (>= 12.2) thor (~> 1.0) - rainbow (3.0.0) - raindrops (0.19.1) + zeitwerk (~> 2.5) + rainbow (3.1.1) rake (13.1.0) - ransack (3.0.1) - activerecord (>= 6.0.4) - activesupport (>= 6.0.4) + ransack (3.2.1) + activerecord (>= 6.1.5) + activesupport (>= 6.1.5) i18n - rb-fsevent (0.10.4) - rb-inotify (0.10.1) - ffi (~> 1.0) - rdiscount (2.2.7) + rdiscount (2.2.7.3) redis (4.8.1) - regexp_parser (2.8.2) + regexp_parser (2.9.0) responders (3.1.1) actionpack (>= 5.2) railties (>= 5.2) @@ -333,42 +324,44 @@ GEM http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 4.0) netrc (~> 0.8) - rexml (3.2.5) - rollbar (2.22.1) - rspec-core (3.10.1) - rspec-support (~> 3.10.0) - rspec-expectations (3.10.1) + rexml (3.2.6) + rspec-core (3.13.0) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.10.0) - rspec-mocks (3.10.2) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.10.0) - rspec-rails (4.0.2) - actionpack (>= 4.2) - activesupport (>= 4.2) - railties (>= 4.2) - rspec-core (~> 3.10) - rspec-expectations (~> 3.10) - rspec-mocks (~> 3.10) - rspec-support (~> 3.10) - rspec-support (3.10.2) - rubocop (1.13.0) + rspec-support (~> 3.13.0) + rspec-rails (6.1.1) + actionpack (>= 6.1) + activesupport (>= 6.1) + railties (>= 6.1) + rspec-core (~> 3.12) + rspec-expectations (~> 3.12) + rspec-mocks (~> 3.12) + rspec-support (~> 3.12) + rspec-support (3.13.0) + rubocop (1.60.2) + json (~> 2.3) + language_server-protocol (>= 3.17.0) parallel (~> 1.10) - parser (>= 3.0.0.0) + parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) - rexml - rubocop-ast (>= 1.2.0, < 2.0) + rexml (>= 3.2.5, < 4.0) + rubocop-ast (>= 1.30.0, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 1.4.0, < 3.0) - rubocop-ast (1.5.0) - parser (>= 3.0.1.1) - rubocop-rails (2.9.1) + unicode-display_width (>= 2.4.0, < 3.0) + rubocop-ast (1.30.0) + parser (>= 3.2.1.0) + rubocop-rails (2.23.1) activesupport (>= 4.2.0) rack (>= 1.1) - rubocop (>= 0.90.0, < 2.0) - ruby-progressbar (1.11.0) - ruby-vips (2.1.4) + rubocop (>= 1.33.0, < 2.0) + rubocop-ast (>= 1.30.0, < 2.0) + ruby-progressbar (1.13.0) + ruby-vips (2.2.0) ffi (~> 1.12) ruby2_keywords (0.0.5) rubyzip (2.3.2) @@ -381,10 +374,11 @@ GEM sprockets-rails tilt select2-rails (4.0.13) - selenium-webdriver (4.1.0) - childprocess (>= 0.5, < 5.0) + selenium-webdriver (4.17.0) + base64 (~> 0.2) rexml (~> 3.2, >= 3.2.5) - rubyzip (>= 1.2.2) + rubyzip (>= 1.2.2, < 3.0) + websocket (~> 1.0) shoulda-matchers (4.5.1) activesupport (>= 4.2.0) sidekiq (6.5.12) @@ -403,8 +397,6 @@ GEM simplecov_json_formatter (~> 0.1) simplecov-html (0.12.3) simplecov_json_formatter (0.1.4) - skylight (5.0.1) - activesupport (>= 5.2.0) sprockets (4.2.1) concurrent-ruby (~> 1.0) rack (>= 2.2.4, < 4) @@ -412,24 +404,16 @@ GEM actionpack (>= 5.2) activesupport (>= 5.2) sprockets (>= 3.0.0) - sshkit (1.21.2) - net-scp (>= 1.1.2) - net-ssh (>= 2.8.0) - thor (1.3.0) - tilt (2.0.10) + thor (1.3.1) + tilt (2.3.0) timeout (0.4.1) - ttfunk (1.7.0) + ttfunk (1.8.0) + bigdecimal (~> 3.1) tzinfo (2.0.6) concurrent-ruby (~> 1.0) uglifier (4.2.0) execjs (>= 0.3.0, < 3) - unf (0.1.4) - unf_ext - unf_ext (0.0.8.2) - unicode-display_width (2.0.0) - unicorn (5.5.5) - kgio (~> 2.6) - raindrops (~> 0.7) + unicode-display_width (2.5.0) warden (1.2.9) rack (>= 2.0.9) web-console (4.1.0) @@ -437,32 +421,27 @@ GEM activemodel (>= 6.0.0) bindex (>= 0.4.0) railties (>= 6.0.0) - webdrivers (5.3.1) - nokogiri (~> 1.6) - rubyzip (>= 1.3.0) - selenium-webdriver (~> 4.0, < 4.11) + websocket (1.2.10) websocket-driver (0.7.6) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.6.12) + zeitwerk (2.6.13) PLATFORMS - ruby + arm64-darwin-22 + x86_64-linux DEPENDENCIES active_storage_validations (~> 1.1.3) activeadmin (~> 2.14) aws-sdk-s3 (~> 1.94) - bootsnap (~> 1.12.0) + bootsnap (~> 1.12) bootstrap-sass (~> 3.4) byebug (~> 11.0) - capistrano (~> 3.1) - capistrano-rails (~> 1.1) - capistrano-rbenv (~> 2.1) capybara (~> 3.29) - database_cleaner (~> 1.8.5) + database_cleaner (~> 2.0) devise (~> 4.9.1) devise-i18n (~> 1.11.0) dotenv-rails (~> 2.7.1) @@ -475,35 +454,31 @@ DEPENDENCIES json_translate (~> 4.0.0) kaminari (~> 1.2.1) letter_opener (~> 1.7.0) - listen (~> 3.2.0) localeapp (~> 3.3) pg (~> 1.4) pg_search (~> 2.3.5) - prawn (~> 2.4.0) + prawn (~> 2.5.0) prawn-table (~> 0.2.2) + puma (~> 6.4) pundit (~> 2.1.0) - rails (~> 6.1.1) + rails (~> 7.0.8) rails-controller-testing rails-i18n (~> 7.0) rdiscount (~> 2.2.7) - rollbar (~> 2.22.1) - rspec-rails (~> 4.0.0) + rspec-rails (~> 6.0) rubocop (~> 1.6) rubocop-rails (~> 2.9) rubyzip (~> 2.3.0) sassc-rails (~> 2.1.2) select2-rails (~> 4.0.13) - selenium-webdriver (~> 4.1.0) + selenium-webdriver (~> 4.16) shoulda-matchers (~> 4.4) sidekiq (~> 6.5) sidekiq-cron (~> 1.9.1) simple_form (~> 5.0.2) simplecov (~> 0.22) - skylight (~> 5.0) uglifier (~> 4.2.0) - unicorn (~> 5.5.1) web-console (~> 4.1.0) - webdrivers (~> 5.3) BUNDLED WITH - 2.1.4 + 2.4.10 diff --git a/README.md b/README.md index 1479d11a2..734ab1c96 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ # TimeOverflow -[![View performance data on Skylight](https://badges.skylight.io/typical/grDTNuzZRnyu.svg)](https://oss.skylight.io/app/applications/grDTNuzZRnyu) [![Build Status](https://github.com/coopdevs/timeoverflow/actions/workflows/ci.yml/badge.svg?branch=develop)](https://github.com/coopdevs/timeoverflow/actions) [![Maintainability](https://api.codeclimate.com/v1/badges/f82c6d98a2441c84f2ef/maintainability)](https://codeclimate.com/github/coopdevs/timeoverflow/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/f82c6d98a2441c84f2ef/test_coverage)](https://codeclimate.com/github/coopdevs/timeoverflow/test_coverage) @@ -41,6 +40,63 @@ as well as being able to post offers / demand ads explained in detail. On the other hand the members can be paid the services of virtual way to save the passage through the office of the Bank of Time and also have the possibility to consult the extract of their account. +## Docker deploying + +This site is ready to be deployed in production with an optimized Docker image which uses two stages in order to minimize the final image size. + +You can locally test the production deployment by the include docker-compose.yml file: + +```bash +docker-compose up +``` + +The first time running it will build the image and setup the database along with some seeds in it (testing data). If the database already exists, it will run migrations (if needed) and just start the application. + +Go to `http://localhost:3000` to see the application running. + +> Note that the current docker-compose.yml is not suitable for a real production deployment, it's just for testing the production Dockerfile locally. +> For production deployment you should use a real database and a reverse proxy like Nginx or Apache (with SSL enabled). +> Refer to the next section in order to see the relevant ENV variables to configure the application. + +### ENV variables + +In order to configure the application you can use the following ENV variables: + +> Make sure to configure at least the ones without a default value (empty). + +| ENV | Description | Default | +| --- | --- | --- | +| `ADMINS` | Space separated list of emails for the superadmins (ie: `admin@timeoverflow.org` | | +| `ALLOWED_HOSTS` | Put here the list of hosts allowed to access the application. Separate with spaces, for instance: `www.timeoverflow.org timeoverflow.org` | `localhost` | +| `RAILS_ENV` | Define the rails environment (not necessary to setup unless you have some special requirements) | `production` | +| `SECRET_KEY_BASE` | Secret key for the application, generate a new one with the command `rails secret` | | +| `DATABASE_URL` | Database URL, the format is `postgresql://user:password@host:port/database` | | +| `RAILS_SERVE_STATIC_FILES` | Tell the application to serve static files (you might want to turn this off if you are using an external web server to serve files from the `public` folder) | `true` | +| `RAILS_LOG_TO_STDOUT` | Tell the application to log to STDOUT (useful for Docker) | `true` | +| `RAILS_LOG_LEVEL` | Log level for the application (use `debug` for maximum information) | `info` | +| `RAILS_MAX_THREADS` | Maximum number of threads to use in the application (use `1` if multithreading is not desired) | `5` | +| `RAILS_MIN_THREADS` | Minimum number of threads to use in the application | `RAILS_MAX_THREADS` value | +| `WEB_CONCURRENCY` | Number of web server processes to use | `2` | +| `RUN_SIDEKIQ` | Run Sidekiq worker process in the docker instance (you might want to change this if want to run different docker instances for Sidekiq and Rails) | `true` | +| `RUN_RAILS` | Run Rails web server process in the docker instance | `true` | +| `SIDEKIQ_CONCURRENCY` | Number of threads to use in Sidekiq | `5` | +| `STORAGE_PROVIDER` | Storage provider for the application (currently the application supports `local` and `amazon`) | `amazon` | +| `FORCE_SSL` | Force SSL connections | `false` | +| `MAIL_LINK_HOST` | Host to use in the links sent by email (use your domain without protocol `mydomain.tld`) | | +| `MAIL_LINK_PROTOCOL` | Protocol to use in the previous host defined for links sent by email | `https` | +| `SMTP_ADDRESS` | SMTP server address (ie: `smtp.mailgun.org`) | | +| `SMTP_PORT` | SMTP server port (ie: `587`) | | +| `SMTP_DOMAIN` | SMTP domain (usually the application's domain) | | +| `SMTP_USER_NAME` | SMTP username | | +| `SMTP_PASSWORD` | SMTP password | | +| `SMTP_AUTHENTICATION` | SMTP authentication method | `plain` | +| `SMTP_ENABLE_STARTTLS_AUTO` | Enable STARTTLS | `true` | +| `SMTP_OPENSSL_VERIFY_MODE` | OpenSSL verify mode | `none` | +| `AWS_ACCESS_KEY_ID` | AWS access key ID (only if `STORAGE_PROVIDER` is `amazon`) | | +| `AWS_SECRET_ACCESS_KEY` | AWS secret access key (only if `STORAGE_PROVIDER` is `amazon`) | | +| `AWS_BUCKET` | AWS bucket name (only if `STORAGE_PROVIDER` is `amazon`) | | +| `AWS_REGION` | AWS region (only if `STORAGE_PROVIDER` is `amazon`) | | + ## Contributions **Join our collaborators team!** diff --git a/app/admin/organization.rb b/app/admin/organization.rb index 62f85c35d..2d8fe6dcc 100644 --- a/app/admin/organization.rb +++ b/app/admin/organization.rb @@ -5,7 +5,7 @@ output = tag.p organization.name if organization.logo.attached? - output << image_tag(organization.logo.variant(resize: "40^x")) + output << image_tag(organization.logo.variant(resize_to_fill: [40, nil])) end output.html_safe @@ -29,7 +29,7 @@ show do div do if organization.logo.attached? - image_tag(organization.logo.variant(resize: "100^x")) + image_tag(organization.logo.variant(resize_to_fill: [100, nil])) end end default_main_content diff --git a/app/admin/post.rb b/app/admin/post.rb index c0d0e5057..9e2f74e53 100644 --- a/app/admin/post.rb +++ b/app/admin/post.rb @@ -30,7 +30,7 @@ end form do |f| - f.semantic_errors *f.object.errors.keys + f.semantic_errors *f.object.errors.attribute_names f.inputs do f.input :type, as: :radio, collection: %w[Offer Inquiry] f.input :title diff --git a/app/admin/user.rb b/app/admin/user.rb index 060f9e844..fe8ec828c 100644 --- a/app/admin/user.rb +++ b/app/admin/user.rb @@ -38,7 +38,7 @@ filter :created_at form do |f| - f.semantic_errors *f.object.errors.keys + f.semantic_errors *f.object.errors.attribute_names f.inputs do f.input :username f.input :email diff --git a/app/assets/javascripts/libs.js b/app/assets/javascripts/libs.js index 1c941bd13..7f1ebda1b 100644 --- a/app/assets/javascripts/libs.js +++ b/app/assets/javascripts/libs.js @@ -1,7 +1,7 @@ //= require jquery2 //= require jquery_ujs -//= require jquery.validate +//= require vendor/jquery.validate //= require bootstrap -//= require highcharts -//= require highcharts-exporting +//= require vendor/highcharts +//= require vendor/highcharts-exporting //= require select2 diff --git a/vendor/assets/javascripts/highcharts-exporting.js b/app/assets/javascripts/vendor/highcharts-exporting.js similarity index 100% rename from vendor/assets/javascripts/highcharts-exporting.js rename to app/assets/javascripts/vendor/highcharts-exporting.js diff --git a/vendor/assets/javascripts/highcharts.js b/app/assets/javascripts/vendor/highcharts.js similarity index 100% rename from vendor/assets/javascripts/highcharts.js rename to app/assets/javascripts/vendor/highcharts.js diff --git a/vendor/assets/javascripts/jquery.validate.js b/app/assets/javascripts/vendor/jquery.validate.js similarity index 100% rename from vendor/assets/javascripts/jquery.validate.js rename to app/assets/javascripts/vendor/jquery.validate.js diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 373c6cee8..5a8e3616b 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -44,9 +44,6 @@ def edit instance_variable_set("@#{resource}", post) end - # GET /offers/:id - # GET /inquiries/:id - # def show post = Post.active.of_active_members.find(params[:id]) update_current_organization!(post.organization) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 5fe37cdd8..eff524c3e 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -8,7 +8,7 @@ def page_title def avatar_url(user, size = 32) user.avatar.attached? ? - user.avatar.variant(resize: "#{size}x#{size}") : + user.avatar.variant(resize_to_fit: [size, size]) : gravatar_url(user, size) end @@ -31,7 +31,7 @@ def organization_logo return if "#{controller_name}##{action_name}".in? %w(organizations#index pages#show) content_tag(:div, class: "row organization-logo") do - image_tag org.logo.variant(resize: "x200^") + image_tag org.logo.variant(resize_to_fit: [200, nil]) end end diff --git a/bin/bundle b/bin/bundle deleted file mode 100755 index 66e9889e8..000000000 --- a/bin/bundle +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env ruby -ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) -load Gem.bin_path('bundler', 'bundle') diff --git a/bin/cap b/bin/cap deleted file mode 100755 index 30352d4d4..000000000 --- a/bin/cap +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env ruby -# -# This file was generated by Bundler. -# -# The application 'cap' is installed as part of a gem, and -# this file is here to facilitate running it. -# - -require 'pathname' -ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", - Pathname.new(__FILE__).realpath) - -require 'rubygems' -require 'bundler/setup' - -load Gem.bin_path('capistrano', 'cap') diff --git a/config/application.rb b/config/application.rb index 2598506d6..810b4ce80 100644 --- a/config/application.rb +++ b/config/application.rb @@ -9,7 +9,7 @@ module Timeoverflow class Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. - config.load_defaults 6.1 + config.load_defaults 7.0 # I18n configuration config.i18n.default_locale = :es @@ -19,10 +19,6 @@ class Application < Rails::Application # This tells Rails to serve error pages from the app itself, rather than using static error pages in public/ config.exceptions_app = self.routes - # Activate the Skylight agent in staging. You need to provision the - # SKYLIGHT_AUTHENTICATION env var for this to work. - config.skylight.environments += ["staging"] - # ActiveJob configuration config.active_job.queue_adapter = :sidekiq @@ -31,9 +27,11 @@ class Application < Rails::Application config.active_record.schema_format = :sql # Guard against DNS rebinding attacks by permitting hosts - config.hosts << 'timeoverflow.local' - config.hosts << 'staging.timeoverflow.org' - config.hosts << 'www.timeoverflow.org' - config.hosts << 'timeoverflow.org' + # localhost is necessary for the docker image + config.hosts = ENV.fetch('ALLOWED_HOSTS', 'localhost').split(' ') + # config.hosts << 'timeoverflow.local' + # config.hosts << 'staging.timeoverflow.org' + # config.hosts << 'www.timeoverflow.org' + # config.hosts << 'timeoverflow.org' end end diff --git a/config/database.yml b/config/database.yml index a840d2a02..55bda3ad0 100644 --- a/config/database.yml +++ b/config/database.yml @@ -1,8 +1,12 @@ defaults: &defaults adapter: postgresql - username: <%= ENV['DATABASE_USER'] || ENV["POSTGRES_USER"] %> + username: <%= ENV['DATABASE_USER'] || ENV["POSTGRES_USER"] || ENV["DATABASE_USERNAME"] %> + password: <%= ENV['DATABASE_PASSWORD'] || ENV["POSTGRES_PASSWORD"] %> + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + host: <%= ENV.fetch("DATABASE_HOST") { "localhost" } %> + port: <%= ENV.fetch("DATABASE_PORT") { "5432" } %> template: 'template0' - encoding: 'UTF8' + encoding: unicode development: <<: *defaults @@ -14,14 +18,7 @@ test: host: localhost password: <%= ENV['DATABASE_PASSWORD'] || ENV["POSTGRES_PASSWORD"] %> -staging: - <<: *defaults - collation: 'es_ES.UTF-8' - ctype: 'es_ES.UTF-8' - database: <%= ENV.fetch('DATABASE_NAME', 'timeoverflow_staging') %> - production: <<: *defaults - collation: 'es_ES.UTF-8' - ctype: 'es_ES.UTF-8' - database: <%= ENV.fetch('DATABASE_NAME', 'timeoverflow_production') %> + <%= "url: #{ENV['DATABASE_URL']}" if ENV['DATABASE_URL'].present? %> + <%= "database: #{ENV.fetch('DATABASE_NAME', 'timeoverflow_production')}" unless ENV['DATABASE_URL'].present? %> diff --git a/config/deploy.rb b/config/deploy.rb deleted file mode 100644 index a58a3f616..000000000 --- a/config/deploy.rb +++ /dev/null @@ -1,81 +0,0 @@ -# config valid only for current version of Capistrano -lock '3.15.0' - -set :application, 'timeoverflow' -set :repo_url, 'git@github.com:coopdevs/timeoverflow.git' - -set :rbenv_type, :user - -# Default branch is :master -ask :branch, `git rev-parse --abbrev-ref HEAD`.chomp - -# Default deploy_to directory is /var/www/my_app_name -# set :deploy_to, '/var/www/my_app_name' - -# Default value for :scm is :git -# set :scm, :git - -# Default value for :format is :pretty -# set :format, :pretty - -# Default value for :log_level is :debug -# set :log_level, :debug - -# Default value for :pty is false -# set :pty, true - -# Default value for :linked_files is [] -set :linked_files, fetch(:linked_files, []).push( - # "config/database.yml", - # "config/secrets.yml", - # ".env", -) - -# Default value for linked_dirs is [] -set :linked_dirs, fetch(:linked_dirs, []).push( - 'log', - 'tmp/pids', - 'tmp/cache', - 'tmp/sockets', - 'vendor/bundle', - 'public/system' -) - -# Default value for default_env is {} -# set :default_env, { path: "/opt/ruby/bin:$PATH" } - -# Default value for keep_releases is 5 -# set :keep_releases, 5 - -namespace :unicorn do - desc 'reload Unicorn' - task :reload do - on roles(:app) do - execute "sudo systemctl reload timeoverflow" - end - end -end - -namespace :sidekiq do - desc 'reload Sidekiq' - task :restart do - on roles(:app) do - execute "sudo systemctl restart sidekiq" - end - end -end - -task "deploy:db:load" do - on primary :db do - within release_path do - with rails_env: fetch(:rails_env) do - execute :rake, "db:schema:load" - end - end - end -end - -before "deploy:migrate", "deploy:db:load" if ENV["COLD"] - -after "deploy:finishing", "unicorn:reload" -after "deploy:finishing", "sidekiq:restart" diff --git a/config/deploy/production.rb b/config/deploy/production.rb deleted file mode 100644 index 240ae0b0a..000000000 --- a/config/deploy/production.rb +++ /dev/null @@ -1 +0,0 @@ -server 'www.timeoverflow.org', user: 'timeoverflow', roles: %w(app db web) diff --git a/config/deploy/staging.rb b/config/deploy/staging.rb deleted file mode 100644 index 59ea3607c..000000000 --- a/config/deploy/staging.rb +++ /dev/null @@ -1 +0,0 @@ -server 'staging.timeoverflow.org', user: 'timeoverflow', roles: %w(app db web) diff --git a/config/environments/development.rb b/config/environments/development.rb index c2189cb2a..000e5f03b 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -64,8 +64,4 @@ # Raises error for missing translations. # config.action_view.raise_on_missing_translations = true - - # Use an evented file watcher to asynchronously detect changes in source code, - # routes, locales, etc. This feature depends on the listen gem. - config.file_watcher = ActiveSupport::EventedFileUpdateChecker end diff --git a/config/environments/production.rb b/config/environments/production.rb index c005b5320..cb2154fda 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -1,3 +1,6 @@ +# Uglifier is only used on the precompile phase, so we can require it conditionally +require "uglifier" if ENV["SECRET_KEY_BASE"] == "dummy" + Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. @@ -20,28 +23,30 @@ # Disable serving static files from the `/public` folder by default since # Apache or NGINX already handles this. - config.public_file_server.enabled = true + config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present? - # Compress CSS & JS using preprocessors. - config.assets.js_compressor = Uglifier.new(harmony: true) - config.assets.css_compressor = :sass + # Compress CSS & JS using preprocessors only on the precompile phase + if ENV["SECRET_KEY_BASE"] == "dummy" + config.assets.js_compressor = Uglifier.new(harmony: true) + config.assets.css_compressor = :sass + end # Do not fallback to assets pipeline if a precompiled asset is missed. - config.assets.compile = true + config.assets.compile = false # Asset digests allow you to set far-future HTTP expiration dates on all assets, # yet still be able to expire them through the digest params. config.assets.digest = true # Enable serving of images, stylesheets, and JavaScripts from an asset server. - # config.action_controller.asset_host = 'http://assets.example.com' + config.asset_host = ENV.fetch("RAILS_ASSET_HOST", nil) if ENV["RAILS_ASSET_HOST"].present? # Specifies the header that your server uses for sending files. # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX # Store uploaded files on the local file system (see config/storage.yml for options). - config.active_storage.service = :amazon + config.active_storage.service = ENV.fetch("STORAGE_PROVIDER", :amazon) # Mount Action Cable outside main process or domain. # config.action_cable.mount_path = nil @@ -49,20 +54,24 @@ # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. - config.force_ssl = true + config.force_ssl = ENV.fetch("FORCE_SSL", "true") == "true" # Use the lowest log level to ensure availability of diagnostic information # when problems arise. - config.log_level = :debug + config.log_level = if %w(debug info warn error fatal).include?(ENV.fetch("RAILS_LOG_LEVEL", nil)) + ENV["RAILS_LOG_LEVEL"] + else + :info + end # Prepend all log lines with the following tags. - config.log_tags = [ :request_id ] + config.log_tags = [:request_id] # Use a different cache store in production. # config.cache_store = :mem_cache_store # Use a real queuing backend for Active Job (and separate queues per environment). - # config.active_job.queue_adapter = :resque + # config.active_job.queue_adapter = :sidekiq # config.active_job.queue_name_prefix = "timeoverflow_production" config.action_mailer.perform_caching = false @@ -74,10 +83,10 @@ config.action_mailer.delivery_method = :smtp config.action_mailer.default_url_options = { host: ENV["MAIL_LINK_HOST"], - protocol: (ENV["MAIL_LINK_PROTO"] || "https") + protocol: ENV["MAIL_LINK_PROTO"] || "https" } - smtp_env = Hash[ENV.map do |k,v| + smtp_env = Hash[ENV.map do |k, v| if /^SMTP_(.*)$/ === k [$1.downcase.to_sym, YAML.load(v)] end diff --git a/config/environments/staging.rb b/config/environments/staging.rb deleted file mode 100644 index cb080f68a..000000000 --- a/config/environments/staging.rb +++ /dev/null @@ -1,2 +0,0 @@ -# Use same settings we use for production -require_relative "production" diff --git a/config/initializers/rollbar.rb b/config/initializers/rollbar.rb deleted file mode 100644 index b31bdac41..000000000 --- a/config/initializers/rollbar.rb +++ /dev/null @@ -1,62 +0,0 @@ -require 'rollbar/rails' -Rollbar.configure do |config| - # Without configuration, Rollbar is enabled in all environments. - # To disable in specific environments, set config.enabled=false. - - config.access_token = ENV['ROLLBAR_ACCESS_TOKEN'] - - # Here we'll disable in 'test': - if Rails.env.test? or ENV['ROLLBAR_ACCESS_TOKEN'].blank? - config.enabled = false - end - - - # By default, Rollbar will try to call the `current_user` controller method - # to fetch the logged-in user object, and then call that object's `id`, - # `username`, and `email` methods to fetch those properties. To customize: - # config.person_method = "my_current_user" - # config.person_id_method = "my_id" - # config.person_username_method = "my_username" - # config.person_email_method = "my_email" - - # If you want to attach custom data to all exception and message reports, - # provide a lambda like the following. It should return a hash. - # config.custom_data_method = lambda { {:some_key => "some_value" } } - - # Add exception class names to the exception_level_filters hash to - # change the level that exception is reported at. Note that if an exception - # has already been reported and logged the level will need to be changed - # via the rollbar interface. - # Valid levels: 'critical', 'error', 'warning', 'info', 'debug', 'ignore' - # 'ignore' will cause the exception to not be reported at all. - # config.exception_level_filters.merge!('MyCriticalException' => 'critical') - # - # You can also specify a callable, which will be called with the exception instance. - - # Ignore the typical "attacks..." - config.exception_level_filters.merge!('ActionController::RoutingError' => lambda { |e| - e.message =~ %r(No route matches \[[A-Z]+\] "/(.+)") - case $1.split("/").first.to_s.downcase - when *%w(myadmin phpmyadmin w00tw00t pma cgi-bin xmlrpc.php wp wordpress cfide) - 'ignore' - else - 'warning' - end - }) - - # Enable asynchronous reporting (uses girl_friday or Threading if girl_friday - # is not installed) - # config.use_async = true - # Supply your own async handler: - # config.async_handler = Proc.new { |payload| - # Thread.new { Rollbar.process_payload(payload) } - # } - - # Enable asynchronous reporting (using sucker_punch) - # config.use_sucker_punch - - # Enable delayed reporting (using Sidekiq) - # config.use_sidekiq - # You can supply custom Sidekiq options: - # config.use_sidekiq 'queue' => 'my_queue' -end diff --git a/config/puma.rb b/config/puma.rb new file mode 100644 index 000000000..db11c1ed7 --- /dev/null +++ b/config/puma.rb @@ -0,0 +1,47 @@ +# 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. +# +max_threads_count = ENV.fetch("RAILS_MAX_THREADS", 5) +min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count } +threads min_threads_count, max_threads_count + +# Specifies the `worker_timeout` threshold that Puma will use to wait before +# terminating a worker in development environments. +# +worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development" + +# Specifies the `port` that Puma will listen on to receive requests; default is 3000. +# +port ENV.fetch("PORT", 3000) + +# Specifies the `environment` that Puma will run in. +# +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 web server processes. If using threads and workers together +# the concurrency of the application would be max `threads` * `workers`. +# Workers do not work on JRuby or Windows (both of which do not support +# processes). +# +if ENV.fetch("RAILS_ENV") == "production" + workers ENV.fetch("WEB_CONCURRENCY", 2) + + # 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. + # + preload_app! +else + # Allow puma to be restarted by `rails restart` command. + plugin :tmp_restart +end diff --git a/config/secrets.yml b/config/secrets.yml index f915c5e4c..296ee64bb 100644 --- a/config/secrets.yml +++ b/config/secrets.yml @@ -4,8 +4,5 @@ development: test: secret_key_base: fde628fa241d74a55d7b0fc8fe1b650091e296d6a4d1e51beba3bfa2ed5c143801c28aaadf318aaefc4423bca781f9715441298052b4f704a8c44f97968feb00 -staging: - secret_key_base: <%= ENV['SECRET_TOKEN'] %> - production: secret_key_base: <%= ENV['SECRET_TOKEN'] %> diff --git a/config/sidekiq.yml b/config/sidekiq.yml index d9d221a4c..25f4cd85c 100644 --- a/config/sidekiq.yml +++ b/config/sidekiq.yml @@ -1,3 +1,4 @@ +:concurrency: <%= ENV.fetch("SIDEKIQ_CONCURRENCY", "5").to_i %> :queues: - default - cron diff --git a/config/unicorn.rb b/config/unicorn.rb deleted file mode 100644 index 68fc5017c..000000000 --- a/config/unicorn.rb +++ /dev/null @@ -1,29 +0,0 @@ -app_path = File.expand_path(File.dirname(__FILE__) + '/..') - -worker_processes 2 - -listen 8080, tcp_nopush: true - -working_directory app_path - -pid app_path + '/tmp/unicorn.pid' - -stderr_path '/var/www/timeoverflow/shared/log/unicorn.err.log' -stdout_path '/var/www/timeoverflow/shared/log/unicorn.std.log' - -# Load the app up before forking -# Combine Ruby 2.0.0+ with "preload_app true" for memory savings -preload_app true - -before_fork do |server, worker| - # the following is highly recomended for Rails + "preload_app true" - # as there's no need for the master process to hold a connection - defined?(ActiveRecord::Base) and - ActiveRecord::Base.connection.disconnect! -end - -after_fork do |server, worker| - # the following is *required* for Rails + "preload_app true" - defined?(ActiveRecord::Base) and - ActiveRecord::Base.establish_connection -end diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..96beab005 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,51 @@ +version: '3' +services: + app: + build: . + volumes: + - ./entrypoint.sh:/app/entrypoint.sh + - ./db/seeds.rb:/app/db/seeds.rb + - ./storage:/app/storage + environment: + - DATABASE_URL=postgres://postgres:timeoverflow@db/timeoverflow + - SECRET_KEY_BASE=d2a645fb46fbd3d4380fb22230ddea4062570eb00853ca5dfe97f8bb1cbff1ad6891c573a4b4b06beb2d0baf59afc9e00794314490a644fc5808ad6cbc3a6379 + - FORCE_SSL=false + - RAILS_LOG_LEVEL=debug + - REDIS_URL=redis://redis:6379/0 + - ADMINS=admin@timeoverflow.org + - STORAGE_PROVIDER=local + ports: + - 3000:3000 + depends_on: + - db + - redis + sidekiq: + build: . + volumes: + - ./entrypoint.sh:/app/entrypoint.sh + - ./storage:/app/storage + environment: + - DATABASE_URL=postgres://postgres:timeoverflow@db/timeoverflow + - SECRET_KEY_BASE=d2a645fb46fbd3d4380fb22230ddea4062570eb00853ca5dfe97f8bb1cbff1ad6891c573a4b4b06beb2d0baf59afc9e00794314490a644fc5808ad6cbc3a6379 + - FORCE_SSL=false + - RAILS_LOG_LEVEL=debug + - REDIS_URL=redis://redis:6379/0 + - ADMINS=admin@timeoverflow.org + - STORAGE_PROVIDER=local + - RUN_SIDEKIQ=true + depends_on: + - db + - redis + db: + image: postgres:14-alpine + environment: + - POSTGRES_PASSWORD=timeoverflow + volumes: + - pg_data:/var/lib/postgresql/data + redis: + image: redis + volumes: + - redis_data:/data +volumes: + pg_data: + redis_data: diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 000000000..f189146b8 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,58 @@ +#!/bin/bash + +# Run rails by default if sidekiq is specified +if [ -z "$RUN_RAILS" ] && [ -z "$RUN_SIDEKIQ" ]; then + RUN_RAILS=true + echo "⚠️ RUN_RAILS and RUN_SIDEKIQ are not set, defaulting to RUN_RAILS=true, RUN_SIDEKIQ=false" +fi + +# ensure booleans +if [ "$RUN_RAILS" == "true" ] || [ "$RUN_RAILS" == "1" ]; then + RUN_RAILS=true +else + RUN_RAILS=false +fi +if [ "$RUN_SIDEKIQ" == "true" ] || [ "$RUN_SIDEKIQ" == "1" ]; then + RUN_SIDEKIQ=true +else + RUN_SIDEKIQ=false +fi + +if [ "$RUN_RAILS" == "true" ]; then + echo "✅ Running Rails" +fi + +if [ "$RUN_SIDEKIQ" == "true" ]; then + echo "✅ Running Sidekiq" +fi + +export RUN_RAILS +export RUN_SIDEKIQ + +# Check all the gems are installed or fails. +bundle check +if [ $? -ne 0 ]; then + echo "❌ Gems in Gemfile are not installed, aborting..." + exit 1 +else + echo "✅ Gems in Gemfile are installed" +fi + +# if no database, run setup +if [ -z "$SKIP_DATABASE_SETUP" ]; then + bundle exec rails db:setup +else + echo "⚠️ Skipping database setup" +fi + +# Check no migrations are pending migrations +if [ -z "$SKIP_MIGRATIONS" ]; then + bundle exec rails db:migrate +else + echo "⚠️ Skipping migrations" +fi + +echo "✅ Migrations are all up" + +echo "🚀 $@" +exec "$@" \ No newline at end of file diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 5a46b6d61..7fa705576 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -10,7 +10,7 @@ img = helper.avatar_url(user, 50) expect(img.class).to eq(ActiveStorage::VariantWithRecord) - expect(img.variation.transformations[:resize]).to eq("50x50") + expect(img.variation.transformations[:resize_to_fit]).to eq([50, 50]) expect(img.blob.filename).to eq("name.png") end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 1ab96aa99..356e0bbc5 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -17,12 +17,11 @@ require 'capybara/rspec' require 'database_cleaner' require 'fabrication' -require 'webdrivers' require 'selenium/webdriver' require 'faker' require 'shoulda/matchers' -Capybara.server = :webrick +Capybara.server = :puma Capybara.register_driver :headless_chrome do |app| browser_options = Selenium::WebDriver::Chrome::Options.new( args: %w(headless disable-gpu no-sandbox) diff --git a/supervisord.conf b/supervisord.conf new file mode 100644 index 000000000..118c3133a --- /dev/null +++ b/supervisord.conf @@ -0,0 +1,25 @@ +[supervisord] +nodaemon=true +logfile=/tmp/supervisord.log +pidfile=/tmp/supervisord.pid + +[program:puma] +command=bin/rails server -b 0.0.0.0 +directory=/app +# If RUN_RAILS is not set, defaults to RUN_SIDEKIQ not being defined +autostart=%(ENV_RUN_RAILS)s +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +[program:sidekiq] +command=bundle exec sidekiq -C config/sidekiq.yml +directory=/app +autostart=%(ENV_RUN_SIDEKIQ)s +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +[supervisorctl] diff --git a/vendor/assets/stylesheets/.keep b/vendor/assets/stylesheets/.keep deleted file mode 100644 index e69de29bb..000000000