diff --git a/.github/workflows/deploy_prodction.yml b/.github/workflows/deploy_production.yml similarity index 70% rename from .github/workflows/deploy_prodction.yml rename to .github/workflows/deploy_production.yml index 83ad03cd..b9bdfdff 100644 --- a/.github/workflows/deploy_prodction.yml +++ b/.github/workflows/deploy_production.yml @@ -10,8 +10,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 + - name: "Install Heroku CLI" + run: curl https://cli-assets.heroku.com/install.sh | sh - uses: akhileshns/heroku-deploy@v3.0.4 with: heroku_api_key: ${{secrets.HEROKU_API_KEY}} heroku_app_name: "kudo-o-matic-production" - heroku_email: "ods@kabisa.nl" \ No newline at end of file + heroku_email: "ods@kabisa.nl" diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index 8e5bbf7d..89ba3a1d 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -3,14 +3,14 @@ name: Rails test and deploy on: push: branches: - - '*' - - '*/*' - - '!master' + - "*" + - "*/*" + - "!master" pull_request: branches: - - '*' - - '*/*' - - '!master' + - "*" + - "*/*" + - "!master" jobs: test: @@ -19,13 +19,13 @@ jobs: POSTGRES_PASSWORD: postgres RAILS_ENV: test - runs-on: ubuntu-latest - + runs-on: ubuntu-24.04 + services: db: image: postgres:10.2 ports: - - 5432:5432 + - 5432:5432 options: >- --health-cmd pg_isready --health-interval 10s @@ -33,41 +33,35 @@ jobs: --health-retries 5 steps: - - uses: actions/checkout@v2 - - uses: ruby/setup-ruby@v1.68.0 - with: - ruby-version: 2.5.4 - - uses: actions/cache@v1 - with: - path: vendor/bundle - key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }} - restore-keys: | - ${{ runner.os }}-gems- - - name: Bundle install - run: | - sudo apt-get -yqq install libpq-dev - gem install bundler -v 2.3.26 - bundle config path vendor/bundle - bundle install --jobs=3 --retry=3 --without production - - name: Setup environment and database - run: | - cp env.example .env - cp config/database.yml.ci config/database.yml - bundle exec rails db:create - bundle exec rails db:migrate - - name: Run tests - run: | - bundle exec rspec + - uses: actions/checkout@v2 + + - name: Install dependencies + run: sudo apt-get update && sudo apt-get install -y libmagickwand-dev imagemagick + + - uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + - name: Setup environment and database + run: | + cp env.example .env + cp config/database.yml.ci config/database.yml + bundle exec rails db:create + bundle exec rails db:migrate + - name: Run tests + run: | + bundle exec rspec deploy-staging: - if: ${{ github.ref == 'refs/heads/develop' }} + if: ${{ github.ref == 'refs/heads/update-heroku-stack' }} needs: test - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v2 + - name: "Install Heroku CLI" + run: curl https://cli-assets.heroku.com/install.sh | sh - uses: akhileshns/heroku-deploy@v3.12.13 with: heroku_api_key: ${{secrets.HEROKU_API_KEY}} heroku_app_name: "kudo-o-matic-staging" - heroku_email: "ods@kabisa.nl" \ No newline at end of file + heroku_email: "ods@kabisa.nl" diff --git a/.gitignore b/.gitignore index 60073dd6..e8e6f8a5 100644 --- a/.gitignore +++ b/.gitignore @@ -13,14 +13,11 @@ *.log /tmp -# Configuration -config/database.yml - # User data exports /exports +dockerfiles/development/db/data *.swp -.* !/.gitignore /public/system/* diff --git a/.ruby-version b/.ruby-version deleted file mode 100644 index fe16b348..00000000 --- a/.ruby-version +++ /dev/null @@ -1 +0,0 @@ -2.5.4 diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 00000000..59511e1d --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +ruby 2.7.8 diff --git a/Gemfile b/Gemfile index 54fc823c..21eb13db 100644 --- a/Gemfile +++ b/Gemfile @@ -1,60 +1,60 @@ + # frozen_string_literal: true source 'https://rubygems.org' -ruby '2.5.4' +ruby '2.7.8' -gem "activerecord-typedstore", "~> 1.1.1" -gem "actionpack", "~> 5.2", ">= 5.2.1" +gem "activerecord-typedstore", "~> 1.6" gem "acts_as_votable", "~> 0.10.0" -gem "autoprefixer-rails", "~> 9.3", ">= 9.3.1" +gem "autoprefixer-rails", "~> 10.4", ">= 10.4.7.0" gem 'aws-sdk-s3', '~> 1.67', '>= 1.67', require: false gem "chronic", "~> 0.10.2" gem "coffee-rails", "~> 4.2", ">= 4.2.2" -gem "devise", "~> 4.7.1" +gem "devise", "~> 4.8" gem 'devise-async', '~> 1.0' gem "doorkeeper", "~> 4.3" gem "delayed_paperclip", "~> 3.0.1" gem "daemons" -gem "draper", "~> 3.0", ">= 3.0.1" +gem "draper", "~> 4.0", ">= 4.0.2" gem "fcm", "~> 0.0.2" gem 'file_validators', '~> 2.3' gem "friendly_id", "~> 5.2.4" gem "google-id-token", "~> 1.4" -gem "graphql", "~> 1.8", ">= 1.8.10" -gem "graphql-batch", "~> 0.3.10" -gem 'graphql-guard', '~> 1.2', '>= 1.2.1' -gem 'graphql_playground-rails', '~> 2.0', '>= 2.0.1' -gem "haml-rails", "~> 1.0" +gem "graphql", "~> 1.10" +gem "graphql-batch", "~> 0.6.0" +# gem 'graphql-guard', '~> 2.0', '>= 2.0.0' +gem 'graphql-playground', "0.1.2" +gem "haml-rails", "~> 2.1" gem "jbuilder", "~> 2.0" +gem 'json', '~> 2.7', '>= 2.7.2' gem "json_web_token", "~> 0.3.5" gem "loofah", "~> 2.3.1" gem 'mini_magick', '~> 4.9', '>= 4.9.2' -gem "nokogiri", "~> 1.10.8" +gem "nokogiri", "~> 1.13.0" gem "paperclip", "~> 6.1" -gem "pg", "~> 0.18" -gem "premailer-rails", "~> 1.9.7" -gem "puma", "~> 3.12" +gem "pg", "~> 1.5" +gem "premailer-rails", "~> 1.10" +gem "puma", "~> 5.0" gem "rabl", "~> 0.13.1" gem "rack-cors", "~> 1.0.5" -gem "rails", "~> 5.2", ">= 5.2.1" -gem "rails-controller-testing", "~> 1.0.2" -gem "rails-html-sanitizer", "~> 1.0.4" +gem "rails", "~> 6.1" +gem "rails-controller-testing", "~> 1.0.5" +gem "rails-html-sanitizer", "~> 1.2.0" gem "rails_autolink", "~> 1.1.6" -gem "railties", "~> 5.2", ">= 5.2.1" -gem 'rmagick', '~> 2.16' -gem "rubocop", "~> 0.60.0", require: false +gem 'rmagick', '~> 5.3.0' +gem "rubocop", "~> 0.83.0", require: false gem "rubyzip", "~> 1.3.0", require: "zip" gem "sentry-raven" gem "settingslogic", "~> 2.0", ">= 2.0.9" gem "scss_lint", "~> 0.54", require: false -gem 'sidekiq', '~> 5.2', '>= 5.2.3' +gem 'sidekiq', '~> 6.0' gem 'slack-ruby-client', '~> 0.14.6' -gem 'apollo_upload_server', '2.0.5' -gem 'image_processing' +gem 'apollo_upload_server', '~> 2.1.0' +gem 'image_processing', '1.12.2' +gem 'ffi', '~> 1.14.2' group :development, :staging, :test do - # GraphQL UI similar to GraphiQL but better gem 'database_cleaner', '~> 1.7' gem 'faker', '~> 1.9', '>= 1.9.1' end @@ -63,33 +63,17 @@ group :development, :test do gem 'dotenv-rails', '~> 2.5' gem 'factory_bot_rails', '~> 4.8', '>= 4.8.2' gem 'graphlient', '~> 0.3.3' - gem 'pry', '~> 0.11.3' -end - -group :test do - gem 'rubocop-rspec', '~> 1.30' - gem 'rspec-rails', '~> 3.8' - gem 'rspec-graphql_matchers', '~> 0.7.1' - gem 'shoulda-matchers', '~> 3.1', '>= 3.1.2' - gem 'simplecov', '~> 0.16.1', require: false + gem 'listen', '~> 3.2' + gem 'pry', '~> 0.13' + gem 'pry-rails' + gem 'rspec-rails', '~> 4.0' + gem 'shoulda-matchers', '~> 5.3' + gem 'simplecov', '~> 0.16.1' + gem 'spring' + gem 'spring-watcher-listen', '~> 2.0.0' gem 'timecop', '~> 0.9.1' end group :development do - # Access an IRB console on exception pages or by using <%= console %> anywhere in the code. gem 'web-console', '>= 3.3.0' - gem 'listen', '~> 3.0.5' - # 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' - # Pry as rails console - gem 'pry-rails' - - gem 'rails-erd' - gem 'railroady' end - -group :production do - gem 'rails_12factor', '~> 0.0.3' -end - diff --git a/Gemfile.lock b/Gemfile.lock index 8f9be2e4..c0cce5ec 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,86 +1,103 @@ GEM remote: https://rubygems.org/ specs: - actioncable (5.2.4.1) - actionpack (= 5.2.4.1) + actioncable (6.1.7.8) + actionpack (= 6.1.7.8) + activesupport (= 6.1.7.8) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailer (5.2.4.1) - actionpack (= 5.2.4.1) - actionview (= 5.2.4.1) - activejob (= 5.2.4.1) + actionmailbox (6.1.7.8) + actionpack (= 6.1.7.8) + activejob (= 6.1.7.8) + activerecord (= 6.1.7.8) + activestorage (= 6.1.7.8) + activesupport (= 6.1.7.8) + mail (>= 2.7.1) + actionmailer (6.1.7.8) + actionpack (= 6.1.7.8) + actionview (= 6.1.7.8) + activejob (= 6.1.7.8) + activesupport (= 6.1.7.8) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (5.2.4.1) - actionview (= 5.2.4.1) - activesupport (= 5.2.4.1) - rack (~> 2.0, >= 2.0.8) + actionpack (6.1.7.8) + actionview (= 6.1.7.8) + activesupport (= 6.1.7.8) + rack (~> 2.0, >= 2.0.9) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.2.4.1) - activesupport (= 5.2.4.1) + rails-html-sanitizer (~> 1.0, >= 1.2.0) + actiontext (6.1.7.8) + actionpack (= 6.1.7.8) + activerecord (= 6.1.7.8) + activestorage (= 6.1.7.8) + activesupport (= 6.1.7.8) + nokogiri (>= 1.8.5) + actionview (6.1.7.8) + activesupport (= 6.1.7.8) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.0.3) - activejob (5.2.4.1) - activesupport (= 5.2.4.1) + rails-html-sanitizer (~> 1.1, >= 1.2.0) + activejob (6.1.7.8) + activesupport (= 6.1.7.8) globalid (>= 0.3.6) - activemodel (5.2.4.1) - activesupport (= 5.2.4.1) - activemodel-serializers-xml (1.0.2) - activemodel (> 5.x) - activesupport (> 5.x) + activemodel (6.1.7.8) + activesupport (= 6.1.7.8) + activemodel-serializers-xml (1.0.3) + activemodel (>= 5.0.0.a) + activesupport (>= 5.0.0.a) builder (~> 3.1) - activerecord (5.2.4.1) - activemodel (= 5.2.4.1) - activesupport (= 5.2.4.1) - arel (>= 9.0) - activerecord-typedstore (1.1.3) - activerecord (>= 4.2, < 5.3) - activestorage (5.2.4.1) - actionpack (= 5.2.4.1) - activerecord (= 5.2.4.1) - marcel (~> 0.3.1) - activesupport (5.2.4.1) + activerecord (6.1.7.8) + activemodel (= 6.1.7.8) + activesupport (= 6.1.7.8) + activerecord-typedstore (1.6.0) + activerecord (>= 6.1) + activestorage (6.1.7.8) + actionpack (= 6.1.7.8) + activejob (= 6.1.7.8) + activerecord (= 6.1.7.8) + activesupport (= 6.1.7.8) + marcel (~> 1.0) + mini_mime (>= 1.1.0) + activesupport (6.1.7.8) concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) + i18n (>= 1.6, < 2) + minitest (>= 5.1) + tzinfo (~> 2.0) + zeitwerk (~> 2.3) acts_as_votable (0.10.0) - addressable (2.5.2) - public_suffix (>= 2.0.2, < 4.0) - apollo_upload_server (2.0.5) - actionpack (>= 4.2) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + apollo_upload_server (2.1.6) + actionpack (>= 6.1.6) graphql (>= 1.8) - arel (9.0.0) - ast (2.4.0) - autoprefixer-rails (9.3.1) - execjs - aws-eventstream (1.1.0) - aws-partitions (1.326.0) - aws-sdk-core (3.98.0) - aws-eventstream (~> 1, >= 1.0.2) - aws-partitions (~> 1, >= 1.239.0) - aws-sigv4 (~> 1.1) - jmespath (~> 1.0) - aws-sdk-kms (1.33.0) - aws-sdk-core (~> 3, >= 3.71.0) - aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.67.1) - aws-sdk-core (~> 3, >= 3.96.1) + ast (2.4.2) + autoprefixer-rails (10.4.19.0) + execjs (~> 2) + aws-eventstream (1.3.0) + aws-partitions (1.982.0) + aws-sdk-core (3.209.1) + aws-eventstream (~> 1, >= 1.3.0) + aws-partitions (~> 1, >= 1.651.0) + aws-sigv4 (~> 1.9) + jmespath (~> 1, >= 1.6.1) + aws-sdk-kms (1.94.0) + aws-sdk-core (~> 3, >= 3.207.0) + aws-sigv4 (~> 1.5) + aws-sdk-s3 (1.166.0) + aws-sdk-core (~> 3, >= 3.207.0) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.1) - aws-sigv4 (1.1.4) - aws-eventstream (~> 1.0, >= 1.0.2) - bcrypt (3.1.13) - bindex (0.5.0) - builder (3.2.4) - choice (0.2.0) + aws-sigv4 (~> 1.5) + aws-sigv4 (1.10.0) + aws-eventstream (~> 1, >= 1.0.2) + base64 (0.2.0) + bcrypt (3.1.20) + bindex (0.8.1) + builder (3.3.0) chronic (0.10.2) climate_control (0.2.0) - coderay (1.1.2) + coderay (1.1.3) coffee-rails (4.2.2) coffee-script (>= 2.2.0) railties (>= 4.0.0) @@ -88,17 +105,18 @@ GEM coffee-script-source execjs coffee-script-source (1.12.2) - concurrent-ruby (1.1.6) - connection_pool (2.2.2) + concurrent-ruby (1.3.4) + connection_pool (2.4.1) crass (1.0.6) - css_parser (1.6.0) + css_parser (1.17.1) addressable - daemons (1.2.6) - database_cleaner (1.7.0) + daemons (1.4.1) + database_cleaner (1.99.0) + date (3.3.4) delayed_paperclip (3.0.1) activejob (>= 4.2) paperclip (>= 3.3) - devise (4.7.1) + devise (4.9.4) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 4.1.0) @@ -107,122 +125,125 @@ GEM devise-async (1.0.0) activejob (>= 5.0) devise (>= 4.0) - diff-lcs (1.3) - docile (1.3.1) + diff-lcs (1.5.1) + docile (1.4.1) doorkeeper (4.4.3) railties (>= 4.2) - dotenv (2.5.0) - dotenv-rails (2.5.0) - dotenv (= 2.5.0) - railties (>= 3.2, < 6.0) - draper (3.0.1) - actionpack (~> 5.0) - activemodel (~> 5.0) - activemodel-serializers-xml (~> 1.0) - activesupport (~> 5.0) - request_store (~> 1.0) - erubi (1.9.0) - erubis (2.7.0) - execjs (2.7.0) + dotenv (2.8.1) + dotenv-rails (2.8.1) + dotenv (= 2.8.1) + railties (>= 3.2) + draper (4.0.2) + actionpack (>= 5.0) + activemodel (>= 5.0) + activemodel-serializers-xml (>= 1.0) + activesupport (>= 5.0) + request_store (>= 1.0) + ruby2_keywords + erubi (1.13.0) + execjs (2.9.1) factory_bot (4.11.1) activesupport (>= 3.0.0) factory_bot_rails (4.11.1) factory_bot (~> 4.11.1) railties (>= 3.0.0) - faker (1.9.1) + faker (1.9.6) i18n (>= 0.7) - faraday (0.15.3) + faraday (0.15.4) multipart-post (>= 1.2, < 3) - faraday_middleware (0.12.2) + faraday_middleware (0.14.0) faraday (>= 0.7.4, < 1.0) - fcm (0.0.2) - httparty - json - ffi (1.9.25) + fcm (0.0.7) + faraday (= 0.15.4) + ffi (1.14.2) file_validators (2.3.0) activemodel (>= 3.2) mime-types (>= 1.0) - friendly_id (5.2.4) + friendly_id (5.2.5) activerecord (>= 4.0.0) - gli (2.19.0) - globalid (0.4.2) - activesupport (>= 4.2.0) + gli (2.21.5) + globalid (1.2.1) + activesupport (>= 6.1) google-id-token (1.4.2) jwt (>= 1) - graphlient (0.3.3) + graphlient (0.3.7) faraday faraday_middleware graphql-client - graphql (1.8.11) - graphql-batch (0.3.10) - graphql (>= 0.8, < 2) + graphql (1.13.23) + base64 + graphql-batch (0.6.0) + graphql (>= 1.12.18, < 3) promise.rb (~> 0.7.2) - graphql-client (0.14.0) - activesupport (>= 3.0, < 6.0) - graphql (~> 1.6) - graphql-guard (1.2.1) - graphql (>= 1.6.0, < 2) - graphql_playground-rails (2.0.1) - rails (~> 5.1, >= 5.1.0) - haml (5.0.4) - temple (>= 0.8.0) + graphql-client (0.23.0) + activesupport (>= 3.0) + graphql (>= 1.13.0) + graphql-playground (0.1.2) + railties (>= 4.1.0) + haml (6.3.0) + temple (>= 0.8.2) + thor tilt - haml-rails (1.0.0) - actionpack (>= 4.0.1) - activesupport (>= 4.0.1) - haml (>= 4.0.6, < 6.0) - html2haml (>= 1.0.1) - railties (>= 4.0.1) - hashie (4.1.0) - html2haml (2.2.0) - erubis (~> 2.7.0) - haml (>= 4.0, < 6) - nokogiri (>= 1.6.0) - ruby_parser (~> 3.5) + haml-rails (2.1.0) + actionpack (>= 5.1) + activesupport (>= 5.1) + haml (>= 4.0.6) + railties (>= 5.1) + hashie (5.0.0) htmlentities (4.3.4) - httparty (0.16.2) - multi_xml (>= 0.5.2) - i18n (1.8.2) + i18n (1.14.6) concurrent-ruby (~> 1.0) - image_processing (1.12.1) + image_processing (1.12.2) mini_magick (>= 4.9.5, < 5) ruby-vips (>= 2.0.17, < 3) - jaro_winkler (1.5.1) - jbuilder (2.7.0) - activesupport (>= 4.2.0) - multi_json (>= 1.2) - jmespath (1.4.0) - json (2.1.0) + jbuilder (2.13.0) + actionview (>= 5.0.0) + activesupport (>= 5.0.0) + jmespath (1.6.2) + json (2.7.2) json_web_token (0.3.5) json (~> 2.1) - jwt (1.5.6) - listen (3.0.8) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) + jwt (2.9.1) + base64 + listen (3.9.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + logger (1.6.1) loofah (2.3.1) crass (~> 1.0.2) nokogiri (>= 1.5.9) - mail (2.7.1) + mail (2.8.1) mini_mime (>= 0.1.1) - marcel (0.3.3) - mimemagic (~> 0.3.2) - method_source (0.9.2) - mime-types (3.2.2) + net-imap + net-pop + net-smtp + marcel (1.0.4) + method_source (1.1.0) + mime-types (3.6.0) + logger mime-types-data (~> 3.2015) - mime-types-data (3.2018.0812) + mime-types-data (3.2024.1001) mimemagic (0.3.10) nokogiri (~> 1) rake - mini_magick (4.9.5) - mini_mime (1.0.2) - mini_portile2 (2.4.0) - minitest (5.14.0) - multi_json (1.13.1) - multi_xml (0.6.0) - multipart-post (2.0.0) - nio4r (2.5.2) - nokogiri (1.10.9) - mini_portile2 (~> 2.4.0) + mini_magick (4.13.2) + mini_mime (1.1.5) + mini_portile2 (2.8.7) + minitest (5.25.1) + multipart-post (2.4.1) + net-imap (0.4.16) + date + net-protocol + net-pop (0.1.2) + net-protocol + net-protocol (0.2.2) + timeout + net-smtp (0.5.0) + net-protocol + nio4r (2.7.3) + nokogiri (1.13.10) + mini_portile2 (~> 2.8.0) + racc (~> 1.4) orm_adapter (0.5.0) paperclip (6.1.0) activemodel (>= 4.2.0) @@ -230,142 +251,132 @@ GEM mime-types mimemagic (~> 0.3.0) terrapin (~> 0.6.0) - parallel (1.12.1) - parser (2.5.3.0) - ast (~> 2.4.0) - pg (0.21.0) - powerpack (0.1.2) - premailer (1.11.1) + parallel (1.26.3) + parser (3.3.5.0) + ast (~> 2.4.1) + racc + pg (1.5.8) + pkg-config (1.5.6) + premailer (1.22.0) addressable - css_parser (>= 1.6.0) + css_parser (>= 1.12.0) htmlentities (>= 4.0.0) - premailer-rails (1.9.7) - actionmailer (>= 3, < 6) + premailer-rails (1.12.0) + actionmailer (>= 3) + net-smtp premailer (~> 1.7, >= 1.7.9) promise.rb (0.7.4) - pry (0.11.3) - coderay (~> 1.1.0) - method_source (~> 0.9.0) - pry-rails (0.3.6) - pry (>= 0.10.4) - public_suffix (3.0.3) - puma (3.12.6) + pry (0.14.2) + coderay (~> 1.1) + method_source (~> 1.0) + pry-rails (0.3.11) + pry (>= 0.13.0) + public_suffix (5.1.1) + puma (5.6.9) + nio4r (~> 2.0) rabl (0.13.1) activesupport (>= 2.3.14) - rack (2.2.3) + racc (1.8.1) + rack (2.2.9) rack-cors (1.0.6) rack (>= 1.6.0) - rack-protection (2.0.4) - rack - rack-test (1.1.0) - rack (>= 1.0, < 3) - railroady (1.5.3) - rails (5.2.4.1) - actioncable (= 5.2.4.1) - actionmailer (= 5.2.4.1) - actionpack (= 5.2.4.1) - actionview (= 5.2.4.1) - activejob (= 5.2.4.1) - activemodel (= 5.2.4.1) - activerecord (= 5.2.4.1) - activestorage (= 5.2.4.1) - activesupport (= 5.2.4.1) - bundler (>= 1.3.0) - railties (= 5.2.4.1) + rack-test (2.1.0) + rack (>= 1.3) + rails (6.1.7.8) + actioncable (= 6.1.7.8) + actionmailbox (= 6.1.7.8) + actionmailer (= 6.1.7.8) + actionpack (= 6.1.7.8) + actiontext (= 6.1.7.8) + actionview (= 6.1.7.8) + activejob (= 6.1.7.8) + activemodel (= 6.1.7.8) + activerecord (= 6.1.7.8) + activestorage (= 6.1.7.8) + activesupport (= 6.1.7.8) + bundler (>= 1.15.0) + railties (= 6.1.7.8) sprockets-rails (>= 2.0.0) - rails-controller-testing (1.0.2) - actionpack (~> 5.x, >= 5.0.1) - actionview (~> 5.x, >= 5.0.1) - activesupport (~> 5.x) - rails-dom-testing (2.0.3) - activesupport (>= 4.2.0) + rails-controller-testing (1.0.5) + actionpack (>= 5.0.1.rc1) + actionview (>= 5.0.1.rc1) + activesupport (>= 5.0.1.rc1) + rails-dom-testing (2.2.0) + activesupport (>= 5.0.0) + minitest nokogiri (>= 1.6) - rails-erd (1.6.0) - activerecord (>= 4.2) - activesupport (>= 4.2) - choice (~> 0.2.0) - ruby-graphviz (~> 1.2) - rails-html-sanitizer (1.0.4) + rails-html-sanitizer (1.2.0) loofah (~> 2.2, >= 2.2.2) - rails_12factor (0.0.3) - rails_serve_static_assets - rails_stdout_logging - rails_autolink (1.1.6) - rails (> 3.1) - rails_serve_static_assets (0.0.5) - rails_stdout_logging (0.0.5) - railties (5.2.4.1) - actionpack (= 5.2.4.1) - activesupport (= 5.2.4.1) + rails_autolink (1.1.8) + actionview (> 3.1) + activesupport (> 3.1) + railties (> 3.1) + railties (6.1.7.8) + actionpack (= 6.1.7.8) + activesupport (= 6.1.7.8) method_source - rake (>= 0.8.7) - thor (>= 0.19.0, < 2.0) - rainbow (3.0.0) - rake (12.3.3) - rb-fsevent (0.10.3) - rb-inotify (0.9.10) - ffi (>= 0.5.0, < 2) - redis (4.0.3) - request_store (1.4.1) + rake (>= 12.2) + thor (~> 1.0) + rainbow (3.1.1) + rake (13.2.1) + rb-fsevent (0.11.2) + rb-inotify (0.11.1) + ffi (~> 1.0) + redis (4.8.1) + request_store (1.7.0) rack (>= 1.4) - responders (3.0.0) - actionpack (>= 5.0) - railties (>= 5.0) - rmagick (2.16.0) - rspec-core (3.8.0) - rspec-support (~> 3.8.0) - rspec-expectations (3.8.2) + responders (3.1.1) + actionpack (>= 5.2) + railties (>= 5.2) + rexml (3.3.8) + rmagick (5.3.0) + pkg-config (~> 1.4) + rspec-core (3.13.1) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.3) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.8.0) - rspec-graphql_matchers (0.7.1) - graphql (>= 0.9, < 2) - rspec-mocks (3.8.0) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.1) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.8.0) - rspec-rails (3.8.1) - actionpack (>= 3.0) - activesupport (>= 3.0) - railties (>= 3.0) - rspec-core (~> 3.8.0) - rspec-expectations (~> 3.8.0) - rspec-mocks (~> 3.8.0) - rspec-support (~> 3.8.0) - rspec-support (3.8.0) - rubocop (0.60.0) - jaro_winkler (~> 1.5.1) + rspec-support (~> 3.13.0) + rspec-rails (4.1.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.13.1) + rubocop (0.83.0) parallel (~> 1.10) - parser (>= 2.5, != 2.5.1.1) - powerpack (~> 0.1) + parser (>= 2.7.0.1) rainbow (>= 2.2.2, < 4.0) + rexml ruby-progressbar (~> 1.7) - unicode-display_width (~> 1.4.0) - rubocop-rspec (1.30.0) - rubocop (>= 0.58.0) - ruby-graphviz (1.2.4) - ruby-progressbar (1.10.0) - ruby-vips (2.0.17) - ffi (~> 1.9) - ruby_parser (3.11.0) - sexp_processor (~> 4.9) + unicode-display_width (>= 1.4.0, < 2.0) + ruby-progressbar (1.13.0) + ruby-vips (2.2.2) + ffi (~> 1.12) + logger + ruby2_keywords (0.0.5) rubyzip (1.3.0) sass (3.7.4) sass-listen (~> 4.0.0) sass-listen (4.0.0) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) - scss_lint (0.57.1) - rake (>= 0.9, < 13) + scss_lint (0.60.0) sass (~> 3.5, >= 3.5.5) - sentry-raven (2.7.4) + sentry-raven (2.13.0) faraday (>= 0.7.6, < 1.0) settingslogic (2.0.9) - sexp_processor (4.11.0) - shoulda-matchers (3.1.2) - activesupport (>= 4.0.0) - sidekiq (5.2.3) - connection_pool (~> 2.2, >= 2.2.2) - rack-protection (>= 1.5.0) - redis (>= 3.3.5, < 5) + shoulda-matchers (5.3.0) + activesupport (>= 5.2.0) + sidekiq (6.5.12) + connection_pool (>= 2.2.5, < 3) + rack (~> 2.0) + redis (>= 4.5.0, < 5) simplecov (0.16.1) docile (~> 1.1) json (>= 1.8, < 3) @@ -378,105 +389,99 @@ GEM gli hashie websocket-driver - spring (2.0.2) - activesupport (>= 4.2) + spring (2.1.1) spring-watcher-listen (2.0.1) listen (>= 2.7, < 4.0) spring (>= 1.2, < 3.0) - sprockets (3.7.2) + sprockets (4.2.1) concurrent-ruby (~> 1.0) - rack (> 1, < 3) - sprockets-rails (3.2.1) - actionpack (>= 4.0) - activesupport (>= 4.0) + rack (>= 2.2.4, < 4) + sprockets-rails (3.5.2) + actionpack (>= 6.1) + activesupport (>= 6.1) sprockets (>= 3.0.0) - temple (0.8.0) + temple (0.10.3) terrapin (0.6.0) climate_control (>= 0.0.3, < 1.0) - thor (1.0.1) - thread_safe (0.3.6) - tilt (2.0.8) - timecop (0.9.1) - tzinfo (1.2.7) - thread_safe (~> 0.1) - unicode-display_width (1.4.0) - warden (1.2.8) - rack (>= 2.0.6) - web-console (3.7.0) - actionview (>= 5.0) - activemodel (>= 5.0) + thor (1.3.2) + tilt (2.4.0) + timecop (0.9.10) + timeout (0.4.1) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + unicode-display_width (1.8.0) + warden (1.2.9) + rack (>= 2.0.9) + web-console (4.2.1) + actionview (>= 6.0.0) + activemodel (>= 6.0.0) bindex (>= 0.4.0) - railties (>= 5.0) - websocket-driver (0.7.1) + railties (>= 6.0.0) + websocket-driver (0.7.6) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) + zeitwerk (2.6.18) PLATFORMS ruby DEPENDENCIES - actionpack (~> 5.2, >= 5.2.1) - activerecord-typedstore (~> 1.1.1) + activerecord-typedstore (~> 1.6) acts_as_votable (~> 0.10.0) - apollo_upload_server (= 2.0.5) - autoprefixer-rails (~> 9.3, >= 9.3.1) + apollo_upload_server (~> 2.1.0) + autoprefixer-rails (~> 10.4, >= 10.4.7.0) aws-sdk-s3 (~> 1.67, >= 1.67) chronic (~> 0.10.2) coffee-rails (~> 4.2, >= 4.2.2) daemons database_cleaner (~> 1.7) delayed_paperclip (~> 3.0.1) - devise (~> 4.7.1) + devise (~> 4.8) devise-async (~> 1.0) doorkeeper (~> 4.3) dotenv-rails (~> 2.5) - draper (~> 3.0, >= 3.0.1) + draper (~> 4.0, >= 4.0.2) factory_bot_rails (~> 4.8, >= 4.8.2) faker (~> 1.9, >= 1.9.1) fcm (~> 0.0.2) + ffi (~> 1.14.2) file_validators (~> 2.3) friendly_id (~> 5.2.4) google-id-token (~> 1.4) graphlient (~> 0.3.3) - graphql (~> 1.8, >= 1.8.10) - graphql-batch (~> 0.3.10) - graphql-guard (~> 1.2, >= 1.2.1) - graphql_playground-rails (~> 2.0, >= 2.0.1) - haml-rails (~> 1.0) - image_processing + graphql (~> 1.10) + graphql-batch (~> 0.6.0) + graphql-playground (= 0.1.2) + haml-rails (~> 2.1) + image_processing (= 1.12.2) jbuilder (~> 2.0) + json (~> 2.7, >= 2.7.2) json_web_token (~> 0.3.5) - listen (~> 3.0.5) + listen (~> 3.2) loofah (~> 2.3.1) mini_magick (~> 4.9, >= 4.9.2) - nokogiri (~> 1.10.8) + nokogiri (~> 1.13.0) paperclip (~> 6.1) - pg (~> 0.18) - premailer-rails (~> 1.9.7) - pry (~> 0.11.3) + pg (~> 1.5) + premailer-rails (~> 1.10) + pry (~> 0.13) pry-rails - puma (~> 3.12) + puma (~> 5.0) rabl (~> 0.13.1) rack-cors (~> 1.0.5) - railroady - rails (~> 5.2, >= 5.2.1) - rails-controller-testing (~> 1.0.2) - rails-erd - rails-html-sanitizer (~> 1.0.4) - rails_12factor (~> 0.0.3) + rails (~> 6.1) + rails-controller-testing (~> 1.0.5) + rails-html-sanitizer (~> 1.2.0) rails_autolink (~> 1.1.6) - railties (~> 5.2, >= 5.2.1) - rmagick (~> 2.16) - rspec-graphql_matchers (~> 0.7.1) - rspec-rails (~> 3.8) - rubocop (~> 0.60.0) - rubocop-rspec (~> 1.30) + rmagick (~> 5.3.0) + rspec-rails (~> 4.0) + rubocop (~> 0.83.0) rubyzip (~> 1.3.0) scss_lint (~> 0.54) sentry-raven settingslogic (~> 2.0, >= 2.0.9) - shoulda-matchers (~> 3.1, >= 3.1.2) - sidekiq (~> 5.2, >= 5.2.3) + shoulda-matchers (~> 5.3) + sidekiq (~> 6.0) simplecov (~> 0.16.1) slack-ruby-client (~> 0.14.6) spring @@ -485,7 +490,7 @@ DEPENDENCIES web-console (>= 3.3.0) RUBY VERSION - ruby 2.5.4p155 + ruby 2.7.8p225 BUNDLED WITH - 2.2.15 + 2.3.22 diff --git a/README.md b/README.md index 8d404766..947c155f 100644 --- a/README.md +++ b/README.md @@ -9,29 +9,59 @@ GitHub issues is closed, the [PivotalTracker project](https://www.pivotaltracker.com/n/projects/1993685) is the place to create new feature requests or bug reports. Please open all new issues in the icebox and use one of the [templates](https://www.pivotaltracker.com/help/articles/story_templates/) when opening a new issue. ## What is the Kudos-o-Matic? -The Kudo project originated from the wish to create common goals for people and teams who work on different projects in order to *strengthen the team spirit*, *collectively celebrate successes* and *ensure transparency* within an organization. -The Kudos-o-Matic was created to keep track of these goals and the progress towards it. -Users can reward each other for good deeds by giving Kudos to each other and work together to achieve common goals in the form of Kudo-thresholds. +The Kudo project originated from the wish to create common goals for people and teams who work on different projects in order to _strengthen the team spirit_, _collectively celebrate successes_ and _ensure transparency_ within an organization. +The Kudos-o-Matic was created to keep track of these goals and the progress towards it. +Users can reward each other for good deeds by giving Kudos to each other and work together to achieve common goals in the form of Kudo-thresholds. ## Quick start guide + ### Prerequisites + To start using the Kudos-o-Matic, you'll need: -* [Ruby](https://www.ruby-lang.org/) (consult [.ruby-version](.ruby-version) or [Gemfile](Gemfile) for the proper version) -* [Ruby on Rails](http://rubyonrails.org/) (consult [Gemfile](Gemfile) for the proper version) -* [Redis](https://redis.io) -* [Bundler](http://bundler.io/) -* [PostgreSQL](https://www.postgresql.org/) -* [ImageMagick](https://www.imagemagick.org/) -* [Mailhog](https://github.com/mailhog/MailHog) (development only) +- [Ruby](https://www.ruby-lang.org/) (consult [.ruby-version](.ruby-version) or [Gemfile](Gemfile) for the proper version) +- [Ruby on Rails](http://rubyonrails.org/) (consult [Gemfile](Gemfile) for the proper version) +- [Redis](https://redis.io) +- [Bundler](http://bundler.io/) +- [PostgreSQL](https://www.postgresql.org/) +- [ImageMagick](https://www.imagemagick.org/) +- [Mailhog](https://github.com/mailhog/MailHog) (development only) ### Setup and Usage #### Ruby and Ruby version managers -By using a Ruby version manager (such as [rbenv](https://github.com/rbenv/rbenv) or [rvm](https://rvm.io/)), you can easily switch between different Ruby versions and avoid conflicts that can occur when different versions use different versions of gems. You can pick your version manager of choice. +By using a Ruby version manager (such as [asdf](https://asdf-vm.com/), you can easily switch between different Ruby versions and avoid conflicts that can occur when different versions use different versions of gems. + +#### Docker for local development + +```sh +docker compose up --build +``` + +When everything runs: + +```sh +open http://localhost:3000/graphql/playground +``` + +To seed the database: + +```sh +docker exec -it kudo-o-matic_web bash +# in the shell of the docker container: +bundle exec rake db:seed +``` + +Running tests: + +```sh +docker exec -it kudo-o-matic_web bash +# in the shell of the docker container: +RAILS_ENV=test bundle exec rspec +``` #### Dependencies @@ -49,23 +79,15 @@ bundle install #### Redis -* For Windows you can download it [here](https://github.com/rgl/redis/downloads) -* For MacOS you can use Homebrew: `brew install redis` +- For Windows you can download it [here](https://github.com/rgl/redis/downloads) +- For MacOS you can use Homebrew: `brew install redis` #### Environment variables Copy environment variables. Following the dependency setup instructions below will help you set these variables. -``` -cp env.example .env ``` - -#### Database configuration - -Copy default database configuration (change if needed) - -```bash -cp config/database.yml.example config/database.yml +cp env.example .env ``` #### Database @@ -87,82 +109,99 @@ bin/rails s ``` #### What's next? -* Your GraphQL endpoint will be listening on ''. -* You can view your Kudos-o-Matic playground at ''. -* You can set up your Kudos-o-Matic front-end by following the instructions on '' + +- Your GraphQL endpoint will be listening on ''. +- You can view your Kudos-o-Matic playground at ''. +- You can set up your Kudos-o-Matic front-end by following the instructions on '' ## Set up dependencies (optional) + Congratulations, you did just set up the Kudos-o-Matic! You can optionally set up the dependencies listed below to get the most out of your Kudos-o-Matic. ### Amazon AWS S3 setup + Follow these instructions to setup the Amazon AWS S3 cloud storage service for images attached to Kudo posts: -* [Create an AWS S3 account](https://aws.amazon.com/resources/create-account/). -* Setup a Amazon S3 Bucket. -* Set the `AWS_S3_HOST_NAME`, `AWS_S3_REGION`, `AWS_S3_BUCKET`, `AWS_S3_BUCKET` and `AWS_SECRET_ACCESS_KEY` environment variables. -* Restart the server. + +- [Create an AWS S3 account](https://aws.amazon.com/resources/create-account/). +- Setup a Amazon S3 Bucket. +- Set the `AWS_S3_HOST_NAME`, `AWS_S3_REGION`, `AWS_S3_BUCKET`, `AWS_S3_BUCKET` and `AWS_SECRET_ACCESS_KEY` environment variables. +- Restart the server. ### Mail setup + The Kudos-o-Matic can automatically send mail notifications when the following events occur: -* When a user joins the Kudos-o-Matic platform. -* When a user receives Kudos (only this user will receive the notification). -* When a Kudo goal is reached. -* Weekly summary mail. -* User account related emails (confirmation, password reset, etc.) + +- When a user joins the Kudos-o-Matic platform. +- When a user receives Kudos (only this user will receive the notification). +- When a Kudo goal is reached. +- Weekly summary mail. +- User account related emails (confirmation, password reset, etc.) To use Mailhog for local usage follow the instructions on [their GitHub repository](https://github.com/mailhog/MailHog) You can also update the configuration (`development.rb`) if you don't want to use Mailhog. Set and use the `MAIL_USERNAME`, `MAIL_PASSWORD` and `MAIL_ADDRESS` environment variables for this. -### Slack +### Slack + See [here](docs/SLACK_INTEGRATION.md). ### CI and deployment + The project is build using [GitHub actions](https://github.com/kabisa/kudo-o-matic/actions) and deployed to [Heroku](https://dashboard.heroku.com/teams/kabisa/apps). Every commit to the develop branch is deployed to [staging](https://dashboard.heroku.com/apps/kudo-o-matic-staging) -Every commit to the master branch is deployed to [production](https://dashboard.heroku.com/apps/kudo-o-matic-production) +Every commit to the master branch is deployed to [production](https://dashboard.heroku.com/apps/kudo-o-matic-production) ## Entities A diagram of the models is available [here](docs/erd.svg). ### KudosMeter -A *KudosMeter* is the base of the Kudos-o-Matic system. It groups *Goals* together and connects them to *Posts*. + +A _KudosMeter_ is the base of the Kudos-o-Matic system. It groups _Goals_ together and connects them to _Posts_. ### Team -A *Team* is the tenant where you and your colleagues give and collect Kudos + +A _Team_ is the tenant where you and your colleagues give and collect Kudos ### Goal -A *Goal* depends on a *KudosMeter*. -To set and see a *Goal* on the Kudo Meter you need to associate the *Goal* with the current *KudosMeter*. -A *Goal* is a common reward for the organization (for example: paintball) that will be organized if the defined Kudo threshold is exceeded. + +A _Goal_ depends on a _KudosMeter_. +To set and see a _Goal_ on the Kudo Meter you need to associate the _Goal_ with the current _KudosMeter_. +A _Goal_ is a common reward for the organization (for example: paintball) that will be organized if the defined Kudo threshold is exceeded. ### Post -A *Post* depends on a *KudosMeter*. -A *User* can reward another *User* for a good deed by creating a Kudo *Post*. + +A _Post_ depends on a _KudosMeter_. +A _User_ can reward another _User_ for a good deed by creating a Kudo _Post_. ### Vote -A *Vote* depends on a *Post* (votable) and a *User* (voter). -A *User* can like and unlike *Posts*. + +A _Vote_ depends on a _Post_ (votable) and a _User_ (voter). +A _User_ can like and unlike _Posts_. ### User -A *User* can create a Kudo *Post* to reward another *User* for a good deed. -*Users* work together to achieve common Kudo *Goals*. + +A _User_ can create a Kudo _Post_ to reward another _User_ for a good deed. +_Users_ work together to achieve common Kudo _Goals_. ## How to contribute? -* [Fork the repository](https://github.com/kabisa/kudo-o-matic/fork). -* Create your feature branch (`git checkout -b my-new-feature`). -* Commit your changes (`git commit -am 'Add some new feature'`). -* Push to the branch (`git push origin my-new-feature`). -* [Create a new Pull Request](https://github.com/kabisa/kudo-o-matic/pulls). + +- [Fork the repository](https://github.com/kabisa/kudo-o-matic/fork). +- Create your feature branch (`git checkout -b my-new-feature`). +- Commit your changes (`git commit -am 'Add some new feature'`). +- Push to the branch (`git push origin my-new-feature`). +- [Create a new Pull Request](https://github.com/kabisa/kudo-o-matic/pulls). ## Did you find a bug? -* [Ensure the bug was not already reported](https://github.com/kabisa/kudo-o-matic/issues). -* If you are unable to find an open issue addressing the problem, [open a new one](https://github.com/kabisa/kudo-o-matic/issues/new). -* Be sure to include a title and a clear description, as much relevant information as possible, -and a code example or an executable test case demonstration of the expected behavior that is not occurring. + +- [Ensure the bug was not already reported](https://github.com/kabisa/kudo-o-matic/issues). +- If you are unable to find an open issue addressing the problem, [open a new one](https://github.com/kabisa/kudo-o-matic/issues/new). +- Be sure to include a title and a clear description, as much relevant information as possible, + and a code example or an executable test case demonstration of the expected behavior that is not occurring. ## License + Copyright (c) 2016-2019 [Kabisa](https://www.kabisa.nl/). See [license](https://github.com/kabisa/kudo-o-matic/blob/develop/LICENSE.md) for details. diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js new file mode 100644 index 00000000..b16e53d6 --- /dev/null +++ b/app/assets/config/manifest.js @@ -0,0 +1,3 @@ +//= link_tree ../images +//= link_directory ../javascripts .js +//= link_directory ../stylesheets .css diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.css similarity index 100% rename from app/assets/stylesheets/application.scss rename to app/assets/stylesheets/application.css diff --git a/app/controllers/graphql_controller.rb b/app/controllers/graphql_controller.rb index 6f99d624..1bdfb20a 100644 --- a/app/controllers/graphql_controller.rb +++ b/app/controllers/graphql_controller.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class GraphqlController < ApplicationController +class GraphQLController < ApplicationController skip_before_action :verify_authenticity_token include AuthenticateUser diff --git a/app/graphql/kudo_o_matic_schema.rb b/app/graphql/kudo_o_matic_schema.rb index 07966ae3..2e12d8dc 100644 --- a/app/graphql/kudo_o_matic_schema.rb +++ b/app/graphql/kudo_o_matic_schema.rb @@ -3,12 +3,11 @@ class KudoOMaticSchema < GraphQL::Schema max_depth 8 # max query nesting use GraphQL::Batch - use GraphQL::Guard.new( # authorization solution - policy_object: Util::GraphqlPolicy, - not_authorized: ->(type, field) do - GraphQL::ExecutionError.new("Not authorized to access #{type}.#{field}") - end - ) + + # Security policies, and the custom 'resolve:' key for graphql fields are + # implemented BaseField class. This class is used as the field_class for + # the BaseObject class. This way, all fields in the schema will inherit + # the security policies and the custom 'resolve' key. query(Types::QueryType) mutation(Types::MutationType) diff --git a/app/graphql/mutations/base_mutation.rb b/app/graphql/mutations/base_mutation.rb index 0349b843..9423e6af 100644 --- a/app/graphql/mutations/base_mutation.rb +++ b/app/graphql/mutations/base_mutation.rb @@ -1,5 +1,7 @@ module Mutations class BaseMutation < GraphQL::Schema::Mutation + field_class Types::BaseField + # methods that should be inherited can go here. # like a `current_tenant` method, or methods related # to the `context` object diff --git a/app/graphql/mutations/goal/create_goal.rb b/app/graphql/mutations/goal/create_goal.rb index ce1850f8..3ac9fab1 100644 --- a/app/graphql/mutations/goal/create_goal.rb +++ b/app/graphql/mutations/goal/create_goal.rb @@ -9,8 +9,8 @@ class Goal::CreateGoal < BaseMutation field :goal, Types::GoalType, null: true def resolve(name:, amount:, kudos_meter_id:) - goal = ::Goal.new(name: name, amount: amount, kudos_meter_id: kudos_meter_id) - + kudos_meter = ::KudosMeter.find(kudos_meter_id) + goal = kudos_meter.goals.build(name: name, amount: amount) if goal.save { goal: goal } else diff --git a/app/graphql/mutations/user/disconnect_slack.rb b/app/graphql/mutations/user/disconnect_slack.rb index 389d5394..32826e73 100644 --- a/app/graphql/mutations/user/disconnect_slack.rb +++ b/app/graphql/mutations/user/disconnect_slack.rb @@ -1,5 +1,7 @@ module Mutations class User::DisconnectSlack < BaseMutation + null true + field :user, Types::UserType, null: true def resolve() @@ -9,7 +11,7 @@ def resolve() user.slack_id = nil if user.save - {user: user} + { user: user } else return Util::ErrorBuilder.build_errors(context, user.errors) end diff --git a/app/graphql/types/base_field.rb b/app/graphql/types/base_field.rb new file mode 100644 index 00000000..fbe406e7 --- /dev/null +++ b/app/graphql/types/base_field.rb @@ -0,0 +1,39 @@ +class Types::BaseField < GraphQL::Schema::Field + def initialize(*args, **kwargs, &block) + + # If the field has a :resolve key, + # add a custom resolver to the field owner, executing the provided Proc. + if kwargs[:resolve] + resolver_name = "resolve_#{kwargs[:name]}".to_sym + kwargs[:resolver_method] = resolver_name + + kwargs[:owner].send(:define_method, resolver_name) do + kwargs[:resolve].call(object, args, context) + end + end + + super(*args, **kwargs, &block) + end + + # Authorisation policy model, based on GraphQL-Guard gem. Uses Util::GraphqlPolicy for policy definitions. + def authorized?(obj, args, ctx) + if (owner && owner.respond_to?(:type_class)) + # Field specific policy + policy = Util::GraphqlPolicy.guard(owner, name.to_sym) + if policy + authorized = policy.call(obj, args, ctx) + raise GraphQL::ExecutionError.new("Not authorized to access #{owner.graphql_name}.#{name}") unless authorized + else + # Object type generic policy + policy = Util::GraphqlPolicy.guard(owner, :'*') + if policy + authorized = policy.call(obj, args, ctx) + raise GraphQL::ExecutionError.new("Not authorized to access #{owner.graphql_name}.#{name}") unless authorized + end + end + end + + super + end + +end \ No newline at end of file diff --git a/app/graphql/types/base_input_object.rb b/app/graphql/types/base_input_object.rb index aa492f17..aebed035 100644 --- a/app/graphql/types/base_input_object.rb +++ b/app/graphql/types/base_input_object.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true module Types - class BaseInputObject < GraphQL::Schema::InputObject; end + class BaseInputObject < GraphQL::Schema::InputObject + end end diff --git a/app/graphql/types/base_interface.rb b/app/graphql/types/base_interface.rb index 3451a195..2950e5b0 100644 --- a/app/graphql/types/base_interface.rb +++ b/app/graphql/types/base_interface.rb @@ -3,5 +3,6 @@ module Types module BaseInterface include GraphQL::Schema::Interface + field_class Types::BaseField end end diff --git a/app/graphql/types/base_object.rb b/app/graphql/types/base_object.rb index 80bc9cb3..532b8bca 100644 --- a/app/graphql/types/base_object.rb +++ b/app/graphql/types/base_object.rb @@ -2,5 +2,6 @@ module Types class BaseObject < GraphQL::Schema::Object + field_class Types::BaseField end end diff --git a/app/graphql/types/date.rb b/app/graphql/types/date.rb index 4f4c2f03..531a4020 100644 --- a/app/graphql/types/date.rb +++ b/app/graphql/types/date.rb @@ -1,19 +1,20 @@ # frozen_string_literal: true module Types - Date = GraphQL::ScalarType.define do - name "Date" + class Date < Types::BaseScalar + graphql_name "Date" description "An ISO 8601-encoded date" - coerce_input ->(value, ctx) do + def self.coerce_input(value, _context) begin - Date.iso8601(value.to_s) + ::Date.iso8601(value.to_s) rescue ArgumentError raise GraphQL::CoercionError, "cannot coerce `#{value.inspect}` to Date" end end - coerce_result ->(value, ctx) { value } + def self.coerce_result(value, _context) + value + end end -end - +end \ No newline at end of file diff --git a/app/graphql/types/goal_type.rb b/app/graphql/types/goal_type.rb index d9e1c76a..4d783c3d 100644 --- a/app/graphql/types/goal_type.rb +++ b/app/graphql/types/goal_type.rb @@ -14,7 +14,7 @@ class GoalType < BaseObject field :achieved_on, Types::Date, null: true, description: 'The date the goal is achieved' - field :kudosMeter, Types::KudosMeterType, + field :kudos_meter, Types::KudosMeterType, null: false, description: 'The kudos meter the goal belongs to', resolve: ->(obj, _args, _ctx) { Util::RecordLoader.for(KudosMeter).load(obj.kudos_meter_id) } diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb index 0ae78a0d..0b9c8acd 100644 --- a/app/graphql/types/query_type.rb +++ b/app/graphql/types/query_type.rb @@ -2,8 +2,6 @@ module Types class QueryType < BaseObject - guard ->(_obj, _args, ctx) { ctx[:current_user].present? } - field :team_by_id, resolver: Queries::TeamByIdQuery field :viewer, resolver: Queries::ViewerQuery diff --git a/app/graphql/types/team_type.rb b/app/graphql/types/team_type.rb index c8707010..3f2496a9 100644 --- a/app/graphql/types/team_type.rb +++ b/app/graphql/types/team_type.rb @@ -26,7 +26,7 @@ class TeamType < Types::BaseObject def posts(order_by:) result = object.posts - result = result.where(kudos_meter_id: active_kudos_meter.id) + result = result.where(kudos_meter_id: object.active_kudos_meter.id) result = result.order(order_by) unless order_by.blank? result end @@ -55,6 +55,7 @@ def kudos_meters(order_by:) null: false, description: 'All goals that belong to the team and are part of the active KudosMeter', resolve: ->(obj, _args, _ctx) { Util::RecordLoader.for(Goal).load_many(obj.active_kudos_meter.goal_ids) } + field :memberships, [Types::TeamMemberType], null: false, description: 'The members of the team' do diff --git a/app/graphql/types/viewer_type.rb b/app/graphql/types/viewer_type.rb index 7fa98e94..aab1f299 100644 --- a/app/graphql/types/viewer_type.rb +++ b/app/graphql/types/viewer_type.rb @@ -8,5 +8,6 @@ class ViewerType < Types::BaseObject null: false, description: 'The current user', resolve: ->(obj, _args, _ctx) { obj } + end end diff --git a/app/graphql/util/error_builder.rb b/app/graphql/util/error_builder.rb index 1167cc35..0be64ffc 100644 --- a/app/graphql/util/error_builder.rb +++ b/app/graphql/util/error_builder.rb @@ -2,9 +2,9 @@ module Util class ErrorBuilder def self.build_errors(context, errors) - errors.map do |attr, message| - message = "#{attr}: #{message}" - context.add_error(GraphQL::ExecutionError.new(message, extensions: { code: 'INPUT_ERROR', attribute: attr })) + errors.each do |error| + message = "#{error.attribute}: #{error.message}" + context.add_error(GraphQL::ExecutionError.new(message, extensions: { code: 'INPUT_ERROR', attribute: error.attribute })) end return end diff --git a/app/graphql/util/graphql_policy.rb b/app/graphql/util/graphql_policy.rb index e1234bc4..4372413b 100644 --- a/app/graphql/util/graphql_policy.rb +++ b/app/graphql/util/graphql_policy.rb @@ -13,7 +13,7 @@ class GraphqlPolicy current_user = ctx[:current_user] return false unless current_user.present? - team = Team.find(args[:teamId]) + team = Team.find(args[:team_id]) current_user.admin? || current_user.admin_of?(team) end, @@ -21,7 +21,7 @@ class GraphqlPolicy current_user = ctx[:current_user] return false unless current_user.present? - team = Guideline.find(args[:guidelineId]).team + team = Guideline.find(args[:guideline_id]).team current_user.admin? || current_user.admin_of?(team) end, @@ -29,7 +29,7 @@ class GraphqlPolicy current_user = ctx[:current_user] return false unless current_user.present? - team = Guideline.find(args[:guidelineId]).team + team = Guideline.find(args[:guideline_id]).team current_user.admin? || current_user.admin_of?(team) end, @@ -39,7 +39,7 @@ class GraphqlPolicy current_user = ctx[:current_user] return false unless current_user.present? - team = KudosMeter.find(args[:kudosMeterId]).team + team = KudosMeter.find(args[:kudos_meter_id]).team current_user.admin? || current_user.admin_of?(team) end, @@ -47,7 +47,7 @@ class GraphqlPolicy current_user = ctx[:current_user] return false unless current_user.present? - team = Goal.find(args[:goalId]).kudos_meter.team + team = Goal.find(args[:goal_id]).kudos_meter.team current_user.admin? || current_user.admin_of?(team) end, @@ -55,7 +55,7 @@ class GraphqlPolicy current_user = ctx[:current_user] return false unless current_user.present? - team = Goal.find(args[:goalId]).kudos_meter.team + team = Goal.find(args[:goal_id]).kudos_meter.team current_user.admin? || current_user.admin_of?(team) end, @@ -65,7 +65,7 @@ class GraphqlPolicy current_user = ctx[:current_user] return false unless current_user.present? - team = Team.find(args[:teamId]) + team = Team.find(args[:team_id]) current_user.admin? || current_user.admin_of?(team) end, @@ -73,7 +73,7 @@ class GraphqlPolicy current_user = ctx[:current_user] return false unless current_user.present? - team = KudosMeter.find(args[:kudosMeterId]).team + team = KudosMeter.find(args[:kudos_meter_id]).team current_user.admin? || current_user.admin_of?(team) end, @@ -81,7 +81,7 @@ class GraphqlPolicy current_user = ctx[:current_user] return false unless current_user.present? - team = KudosMeter.find(args[:kudosMeterId]).team + team = KudosMeter.find(args[:kudos_meter_id]).team current_user.admin? || current_user.admin_of?(team) end, @@ -89,14 +89,14 @@ class GraphqlPolicy current_user = ctx[:current_user] return false unless current_user.present? - team = Team.find(args[:teamId]) + team = Team.find(args[:team_id]) current_user.admin? || current_user.admin_of?(team) end, ### Post createPost: ->(_obj, args, ctx) do - team = Team.find(args[:teamId]) + team = Team.find(args[:team_id]) current_user = ctx[:current_user] return false unless current_user.present? @@ -123,7 +123,7 @@ class GraphqlPolicy current_user = ctx[:current_user] return false unless current_user.present? - team = Team.find(args[:teamId]) + team = Team.find(args[:team_id]) current_user.admin? || current_user.admin_of?(team) }, @@ -131,7 +131,7 @@ class GraphqlPolicy current_user = ctx[:current_user] return false unless current_user.present? - team = Team.find(args[:teamId]) + team = Team.find(args[:team_id]) current_user.admin? || current_user.admin_of?(team) end, @@ -141,7 +141,7 @@ class GraphqlPolicy current_user = ctx[:current_user] return false unless current_user.present? - team = Team.find(args[:teamId]) + team = Team.find(args[:team_id]) current_user.admin? || current_user.admin_of?(team) end, @@ -149,12 +149,12 @@ class GraphqlPolicy current_user = ctx[:current_user] return false unless current_user.present? - team = TeamInvite.find(args[:teamInviteId]).team + team = TeamInvite.find(args[:team_invite_id]).team current_user.admin? || current_user.admin_of?(team) end, acceptTeamInvite: ->(_obj, args, ctx) do - team_invite = TeamInvite.find(args[:teamInviteId]) + team_invite = TeamInvite.find(args[:team_invite_id]) current_user = ctx[:current_user] return false unless current_user.present? @@ -164,7 +164,7 @@ class GraphqlPolicy current_user = ctx[:current_user] return false unless current_user.present? - team_invite = TeamInvite.find(args[:teamInviteId]) + team_invite = TeamInvite.find(args[:team_invite_id]) current_user.admin? || team_invite.email == current_user.email end, @@ -182,7 +182,7 @@ class GraphqlPolicy current_user = ctx[:current_user] return false unless current_user.present? - team = Team.find(args[:teamId]) + team = Team.find(args[:team_id]) current_user.admin? || current_user.admin_of?(team) end, @@ -193,7 +193,7 @@ class GraphqlPolicy ### Vote toggleLikePost: ->(_obj, args, ctx) do - team = Post.find(args[:postId]).team + team = Post.find(args[:post_id]).team current_user = ctx[:current_user] return false unless current_user.present? @@ -204,24 +204,21 @@ class GraphqlPolicy # Team Type ################# Types::TeamType => { - '*': ->(obj, _args, ctx) do + '*': ->(team, _args, ctx) do current_user = ctx[:current_user] - team = obj.object current_user.member_of?(team) || current_user.admin? end, - id: ->(obj, _args, ctx) do + id: ->(team, _args, ctx) do current_user = ctx[:current_user] - team = obj.object TeamInvite.where(team: team, email: current_user.email).any? || current_user.member_of?(team) || current_user.admin? end, - name: ->(obj, _args, ctx) do + name: ->(team, _args, ctx) do current_user = ctx[:current_user] - team = obj.object TeamInvite.where(team: team, email: current_user.email).any? || current_user.member_of?(team) || @@ -232,9 +229,8 @@ class GraphqlPolicy # TeamInvite Type ################# Types::TeamInviteType => { - '*': ->(obj, _args, ctx) do + '*': ->(team_invite, _args, ctx) do current_user = ctx[:current_user] - team_invite = obj.object current_user.email == team_invite.email || current_user.admin_of?(team_invite.team) || @@ -245,11 +241,10 @@ class GraphqlPolicy # Post Type ################# Types::PostType => { - '*': ->(obj, _args, ctx) do + '*': ->(post, _args, ctx) do current_user = ctx[:current_user] - team = obj.object.team - current_user.member_of?(team) || + current_user.member_of?(post.team) || current_user.admin? end }, @@ -257,10 +252,8 @@ class GraphqlPolicy # User Type ################# Types::UserType => { - 'email': ->(obj, _args, ctx) do + 'email': ->(user, _args, ctx) do current_user = ctx[:current_user] - user = obj.object - same_teams = (user.teams && current_user.teams) if same_teams.any? @@ -269,11 +262,16 @@ class GraphqlPolicy current_user.id == user.id || current_user.admin? end end + }, + Types::QueryType => { + '*': ->(_obj, _args, ctx) do + ctx[:current_user].present? + end } }.freeze def self.guard(type, field) - RULES.dig(type.metadata[:type_class], field) + RULES.dig(type.type_class, field) end end end \ No newline at end of file diff --git a/app/mailers/post_mailer.rb b/app/mailers/post_mailer.rb index 2a6f7be8..83be2ea6 100644 --- a/app/mailers/post_mailer.rb +++ b/app/mailers/post_mailer.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true class PostMailer < ApplicationMailer - add_template_helper(ApplicationHelper) - add_template_helper(GoalHelper) + helper ApplicationHelper + helper GoalHelper def self.new_post(post) return if post.receivers.nil? diff --git a/compose.yml b/compose.yml new file mode 100644 index 00000000..74e8a9fc --- /dev/null +++ b/compose.yml @@ -0,0 +1,71 @@ +services: + # Rails application + web: + container_name: kudo-o-matic_web + build: + context: . + dockerfile: ./dockerfiles/development/web/Dockerfile + ports: + - "${WEB_PORT_LOCAL:-3000}:3000" + volumes: + - .:/code + depends_on: + db: + condition: service_healthy + db-test: + condition: service_healthy + redis: + condition: service_healthy + command: bash -c "bundle install + && bundle exec rake db:migrate + && bundle exec puma" + healthcheck: + test: curl --fail http://localhost:3000 || exit 1 + interval: 20s + retries: 5 + start_period: 20s + timeout: 10s + + # Postgres + db: + image: postgres:16 + environment: + POSTGRES_USER: "${DATABASE_USER:-kudo_user}" + POSTGRES_PASSWORD: "${DATABASE_PASSWORD:-kudos}" + POSTGRES_DB: "${DATABASE_NAME:-kudo-o-matic_development}" + healthcheck: + test: pg_isready -d $${POSTGRES_DB} -U $$POSTGRES_USER + interval: 5s + timeout: 10s + retries: 10 + ports: + - "${DATABASE_PORT_LOCAL:-9001}:5432" + volumes: + # Init script that creates a database from the dump file listed below if no database exists yet + - ./dockerfiles/development/db/init.sh:/docker-entrypoint-initdb.d/init.sh + # Data volume for persistent storage + - ./dockerfiles/development/db/data:/var/lib/postgresql/data + + db-test: + image: postgres:16 + environment: + POSTGRES_USER: "${DATABASE_USER:-kudo_user}" + POSTGRES_PASSWORD: "${DATABASE_PASSWORD:-kudos}" + POSTGRES_DB: "${TEST_DATABASE_NAME:-kudo-o-matic_test}" + healthcheck: + test: pg_isready -d $${POSTGRES_DB} -U $$POSTGRES_USER + interval: 5s + timeout: 10s + retries: 10 + ports: + - "${DATABASE_PORT_LOCAL:-9002}:5432" + + redis: + image: redis:7 + ports: + - "${REDIS_PORT_LOCAL:-6379}:6379" + healthcheck: + test: ["CMD", "redis-cli", "--raw", "incr", "ping"] + interval: 1s + timeout: 3s + retries: 5 diff --git a/config/database.yml b/config/database.yml new file mode 100644 index 00000000..1ec16a3a --- /dev/null +++ b/config/database.yml @@ -0,0 +1,26 @@ +default: &default + adapter: postgresql + encoding: unicode + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 10 } %> + +development: + <<: *default + database: <%= ENV.fetch("DATABASE_NAME", "kudo-o-matic_development") %> + url: <%= ENV.fetch("DATABASE_URL", "postgres://kudo_user:kudos@db:5432") %> + +test: + <<: *default + database: <%= ENV.fetch("DATABASE_NAME_TEST", "kudo-o-matic_test") %> + url: <%= ENV.fetch("DATABASE_URL", "postgres://kudo_user:kudos@db:5432") %> + +staging: + <<: *default + database: kudo-o-matic_staging + username: kudo-o-matic + password: <%= ENV['KUDO-O-MATIC_DATABASE_PASSWORD'] %> + +production: + <<: *default + database: kudo-o-matic_production + username: kudo-o-matic + password: <%= ENV['KUDO-O-MATIC_DATABASE_PASSWORD'] %> diff --git a/config/database.yml.example b/config/database.yml.example deleted file mode 100644 index cc323859..00000000 --- a/config/database.yml.example +++ /dev/null @@ -1,91 +0,0 @@ -# PostgreSQL. Versions 8.2 and up are supported. -# -# Install the pg driver: -# gem install pg -# On OS X with Homebrew: -# gem install pg -- --with-pg-config=/usr/local/bin/pg_config -# On OS X with MacPorts: -# gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config -# On Windows: -# gem install pg -# Choose the win32 build. -# Install PostgreSQL and put its /bin directory on your path. -# -# Configure Using Gemfile -# gem 'pg' -# -default: &default - adapter: postgresql - encoding: unicode - # For details on connection pooling, see rails configuration guide - # http://guides.rubyonrails.org/configuring.html#database-pooling - pool: 5 - -development: - <<: *default - database: kudo-o-matic_development - - # The specified database role being used to connect to postgres. - # To create additional roles in postgres see `$ createuser --help`. - # When left blank, postgres will use the default role. This is - # the same name as the operating system user that initialized the database. - #username: kudo-o-matic - - # The password associated with the postgres role (username). - #password: - - # Connect on a TCP socket. Omitted by default since the client uses a - # domain socket that doesn't need configuration. Windows does not have - # domain sockets, so uncomment these lines. - #host: localhost - - # The TCP port the server listens on. Defaults to 5432. - # If your server runs on a different port number, change accordingly. - #port: 5432 - - # Schema search path. The server defaults to $user,public - #schema_search_path: myapp,sharedapp,public - - # Minimum log levels, in increasing order: - # debug5, debug4, debug3, debug2, debug1, - # log, notice, warning, error, fatal, and panic - # Defaults to warning. - #min_messages: notice - -# Warning: The database defined as "test" will be erased and -# re-generated from your development database when you run "rake". -# Do not set this db to the same as development or production. -test: - <<: *default - database: kudo-o-matic_test - -# As with config/secrets.yml, you never want to store sensitive information, -# like your database password, in your source code. If your source code is -# ever seen by anyone, they now have access to your database. -# -# Instead, provide the password as a unix environment variable when you boot -# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database -# for a full rundown on how to provide these environment variables in a -# production deployment. -# -# On Heroku and other platform providers, you may have a full connection URL -# available as an environment variable. For example: -# -# DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase" -# -# You can use this database configuration with: -# -# production: -# url: <%= ENV['DATABASE_URL'] %> -# -staging: - <<: *default - database: kudo-o-matic_staging - username: kudo-o-matic - password: <%= ENV['KUDO-O-MATIC_DATABASE_PASSWORD'] %> - -production: - <<: *default - database: kudo-o-matic_production - username: kudo-o-matic - password: <%= ENV['KUDO-O-MATIC_DATABASE_PASSWORD'] %> diff --git a/config/environments/staging.rb b/config/environments/staging.rb index ba778c31..1e955042 100644 --- a/config/environments/staging.rb +++ b/config/environments/staging.rb @@ -70,7 +70,7 @@ user_name: ENV["MAIL_USERNAME"], password: ENV["MAIL_PASSWORD"], authentication: ENV["MAIL_AUTHENTICATION"] || "plain", - enable_starttls_auto: ENV["MAIL_ENABLE_STARTTLS_AUTO"] || true, + enable_starttls_auto: ENV["MAIL_ENABLE_STARTTLS_AUTO"].to_s.downcase == "true" || true, } config.action_mailer.default_url_options = { host: "kudo-o-matic-staging.dokku.kabisa.io" } diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb new file mode 100644 index 00000000..44e79c77 --- /dev/null +++ b/config/initializers/sidekiq.rb @@ -0,0 +1,10 @@ +# Disable SSL verification as per Heroku requirements +# See https://devcenter.heroku.com/articles/connecting-heroku-redis#connecting-in-rails + +Sidekiq.configure_server do |config| + config.redis = { ssl_params: { verify_mode: OpenSSL::SSL::VERIFY_NONE } } +end + +Sidekiq.configure_client do |config| + config.redis = { ssl_params: { verify_mode: OpenSSL::SSL::VERIFY_NONE } } +end diff --git a/config/routes.rb b/config/routes.rb index fbde50d3..530766d7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,7 +1,9 @@ # frozen_string_literal: true Rails.application.routes.draw do - mount GraphqlPlayground::Rails::Engine, at: "/graphql/playground", graphql_path: "/graphql" + if Rails.env.development? + mount GraphQL::Playground::Engine, at: "/graphql/playground", graphql_path: "/graphql" + end root 'application#index' diff --git a/db/migrate/20241010140417_add_service_name_to_active_storage_blobs.active_storage.rb b/db/migrate/20241010140417_add_service_name_to_active_storage_blobs.active_storage.rb new file mode 100644 index 00000000..a15c6ce8 --- /dev/null +++ b/db/migrate/20241010140417_add_service_name_to_active_storage_blobs.active_storage.rb @@ -0,0 +1,22 @@ +# This migration comes from active_storage (originally 20190112182829) +class AddServiceNameToActiveStorageBlobs < ActiveRecord::Migration[6.0] + def up + return unless table_exists?(:active_storage_blobs) + + unless column_exists?(:active_storage_blobs, :service_name) + add_column :active_storage_blobs, :service_name, :string + + if configured_service = ActiveStorage::Blob.service.name + ActiveStorage::Blob.unscoped.update_all(service_name: configured_service) + end + + change_column :active_storage_blobs, :service_name, :string, null: false + end + end + + def down + return unless table_exists?(:active_storage_blobs) + + remove_column :active_storage_blobs, :service_name + end +end diff --git a/db/migrate/20241010140418_create_active_storage_variant_records.active_storage.rb b/db/migrate/20241010140418_create_active_storage_variant_records.active_storage.rb new file mode 100644 index 00000000..94ac83af --- /dev/null +++ b/db/migrate/20241010140418_create_active_storage_variant_records.active_storage.rb @@ -0,0 +1,27 @@ +# This migration comes from active_storage (originally 20191206030411) +class CreateActiveStorageVariantRecords < ActiveRecord::Migration[6.0] + def change + return unless table_exists?(:active_storage_blobs) + + # Use Active Record's configured type for primary key + create_table :active_storage_variant_records, id: primary_key_type, if_not_exists: true do |t| + t.belongs_to :blob, null: false, index: false, type: blobs_primary_key_type + t.string :variation_digest, null: false + + t.index %i[ blob_id variation_digest ], name: "index_active_storage_variant_records_uniqueness", unique: true + t.foreign_key :active_storage_blobs, column: :blob_id + end + end + + private + def primary_key_type + config = Rails.configuration.generators + config.options[config.orm][:primary_key_type] || :primary_key + end + + def blobs_primary_key_type + pkey_name = connection.primary_key(:active_storage_blobs) + pkey_column = connection.columns(:active_storage_blobs).find { |c| c.name == pkey_name } + pkey_column.bigint? ? :bigint : pkey_column.type + end +end diff --git a/db/seeds.rb b/db/seeds.rb index 0aecdda8..4137a84c 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -16,7 +16,7 @@ users.each do |user| user = User.create( name: user, - email: "#{user}@example.com", + email: "#{user.downcase}@example.com", password: 'password', password_confirmation: 'password' ) diff --git a/db/structure.sql b/db/structure.sql index 20e7884d..2693a655 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -69,7 +69,8 @@ CREATE TABLE public.active_storage_blobs ( metadata text, byte_size bigint NOT NULL, checksum character varying NOT NULL, - created_at timestamp without time zone NOT NULL + created_at timestamp without time zone NOT NULL, + service_name character varying NOT NULL ); @@ -92,6 +93,36 @@ CREATE SEQUENCE public.active_storage_blobs_id_seq ALTER SEQUENCE public.active_storage_blobs_id_seq OWNED BY public.active_storage_blobs.id; +-- +-- Name: active_storage_variant_records; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.active_storage_variant_records ( + id bigint NOT NULL, + blob_id bigint NOT NULL, + variation_digest character varying NOT NULL +); + + +-- +-- Name: active_storage_variant_records_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.active_storage_variant_records_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: active_storage_variant_records_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.active_storage_variant_records_id_seq OWNED BY public.active_storage_variant_records.id; + + -- -- Name: activities; Type: TABLE; Schema: public; Owner: - -- @@ -109,6 +140,7 @@ CREATE TABLE public.activities ( -- CREATE SEQUENCE public.activities_id_seq + AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE @@ -147,7 +179,7 @@ CREATE TABLE public.exports ( updated_at timestamp without time zone NOT NULL, zip_file_name character varying, zip_content_type character varying, - zip_file_size integer, + zip_file_size bigint, zip_updated_at timestamp without time zone ); @@ -157,6 +189,7 @@ CREATE TABLE public.exports ( -- CREATE SEQUENCE public.exports_id_seq + AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE @@ -189,6 +222,7 @@ CREATE TABLE public.fcm_tokens ( -- CREATE SEQUENCE public.fcm_tokens_id_seq + AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE @@ -222,6 +256,7 @@ CREATE TABLE public.friendly_id_slugs ( -- CREATE SEQUENCE public.friendly_id_slugs_id_seq + AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE @@ -256,6 +291,7 @@ CREATE TABLE public.goals ( -- CREATE SEQUENCE public.goals_id_seq + AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE @@ -289,6 +325,7 @@ CREATE TABLE public.guidelines ( -- CREATE SEQUENCE public.guidelines_id_seq + AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE @@ -322,6 +359,7 @@ CREATE TABLE public.kudos_meters ( -- CREATE SEQUENCE public.kudos_meters_id_seq + AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE @@ -358,6 +396,7 @@ CREATE TABLE public.oauth_access_grants ( -- CREATE SEQUENCE public.oauth_access_grants_id_seq + AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE @@ -395,6 +434,7 @@ CREATE TABLE public.oauth_access_tokens ( -- CREATE SEQUENCE public.oauth_access_tokens_id_seq + AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE @@ -430,6 +470,7 @@ CREATE TABLE public.oauth_applications ( -- CREATE SEQUENCE public.oauth_applications_id_seq + AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE @@ -488,7 +529,7 @@ CREATE TABLE public.posts ( updated_at timestamp without time zone NOT NULL, image_file_name character varying, image_content_type character varying, - image_file_size integer, + image_file_size bigint, image_updated_at timestamp without time zone, slack_reaction_created_at character varying, slack_transaction_updated_at character varying, @@ -503,6 +544,7 @@ CREATE TABLE public.posts ( -- CREATE SEQUENCE public.posts_id_seq + AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE @@ -533,7 +575,7 @@ CREATE TABLE public.schema_migrations ( CREATE TABLE public.team_invites ( id integer NOT NULL, team_id integer, - sent_at timestamp without time zone DEFAULT now(), + sent_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP, accepted_at timestamp without time zone, declined_at timestamp without time zone, email character varying @@ -545,6 +587,7 @@ CREATE TABLE public.team_invites ( -- CREATE SEQUENCE public.team_invites_id_seq + AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE @@ -581,6 +624,7 @@ CREATE TABLE public.team_members ( -- CREATE SEQUENCE public.team_members_id_seq + AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE @@ -607,7 +651,7 @@ CREATE TABLE public.teams ( updated_at timestamp without time zone NOT NULL, logo_file_name character varying, logo_content_type character varying, - logo_file_size integer, + logo_file_size bigint, logo_updated_at timestamp without time zone, slug character varying, preferences json, @@ -623,6 +667,7 @@ CREATE TABLE public.teams ( -- CREATE SEQUENCE public.teams_id_seq + AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE @@ -685,6 +730,7 @@ CREATE TABLE public.users ( -- CREATE SEQUENCE public.users_id_seq + AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE @@ -712,8 +758,8 @@ CREATE TABLE public.votes ( vote_flag boolean, vote_scope character varying, vote_weight integer, - created_at timestamp without time zone, - updated_at timestamp without time zone + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL ); @@ -722,6 +768,7 @@ CREATE TABLE public.votes ( -- CREATE SEQUENCE public.votes_id_seq + AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE @@ -750,6 +797,13 @@ ALTER TABLE ONLY public.active_storage_attachments ALTER COLUMN id SET DEFAULT n ALTER TABLE ONLY public.active_storage_blobs ALTER COLUMN id SET DEFAULT nextval('public.active_storage_blobs_id_seq'::regclass); +-- +-- Name: active_storage_variant_records id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.active_storage_variant_records ALTER COLUMN id SET DEFAULT nextval('public.active_storage_variant_records_id_seq'::regclass); + + -- -- Name: activities id; Type: DEFAULT; Schema: public; Owner: - -- @@ -885,6 +939,14 @@ ALTER TABLE ONLY public.active_storage_blobs ADD CONSTRAINT active_storage_blobs_pkey PRIMARY KEY (id); +-- +-- Name: active_storage_variant_records active_storage_variant_records_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.active_storage_variant_records + ADD CONSTRAINT active_storage_variant_records_pkey PRIMARY KEY (id); + + -- -- Name: activities activities_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -989,6 +1051,14 @@ ALTER TABLE ONLY public.posts ADD CONSTRAINT posts_pkey PRIMARY KEY (id); +-- +-- Name: schema_migrations schema_migrations_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.schema_migrations + ADD CONSTRAINT schema_migrations_pkey PRIMARY KEY (version); + + -- -- Name: team_invites team_invites_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -1050,6 +1120,13 @@ CREATE UNIQUE INDEX index_active_storage_attachments_uniqueness ON public.active CREATE UNIQUE INDEX index_active_storage_blobs_on_key ON public.active_storage_blobs USING btree (key); +-- +-- Name: index_active_storage_variant_records_uniqueness; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX index_active_storage_variant_records_uniqueness ON public.active_storage_variant_records USING btree (blob_id, variation_digest); + + -- -- Name: index_exports_on_user_id; Type: INDEX; Schema: public; Owner: - -- @@ -1232,6 +1309,13 @@ CREATE UNIQUE INDEX index_users_on_unlock_token ON public.users USING btree (unl CREATE INDEX index_votes_on_votable_id_and_votable_type_and_vote_scope ON public.votes USING btree (votable_id, votable_type, vote_scope); +-- +-- Name: index_votes_on_votable_type_and_votable_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_votes_on_votable_type_and_votable_id ON public.votes USING btree (votable_type, votable_id); + + -- -- Name: index_votes_on_voter_id_and_voter_type_and_vote_scope; Type: INDEX; Schema: public; Owner: - -- @@ -1240,10 +1324,10 @@ CREATE INDEX index_votes_on_voter_id_and_voter_type_and_vote_scope ON public.vot -- --- Name: unique_schema_migrations; Type: INDEX; Schema: public; Owner: - +-- Name: index_votes_on_voter_type_and_voter_id; Type: INDEX; Schema: public; Owner: - -- -CREATE UNIQUE INDEX unique_schema_migrations ON public.schema_migrations USING btree (version); +CREATE INDEX index_votes_on_voter_type_and_voter_id ON public.votes USING btree (voter_type, voter_id); -- @@ -1278,6 +1362,14 @@ ALTER TABLE ONLY public.oauth_access_tokens ADD CONSTRAINT fk_rails_732cb83ab7 FOREIGN KEY (application_id) REFERENCES public.oauth_applications(id); +-- +-- Name: active_storage_variant_records fk_rails_993965df05; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.active_storage_variant_records + ADD CONSTRAINT fk_rails_993965df05 FOREIGN KEY (blob_id) REFERENCES public.active_storage_blobs(id); + + -- -- Name: team_members fk_rails_9ec2d5e75e; Type: FK CONSTRAINT; Schema: public; Owner: - -- @@ -1390,6 +1482,8 @@ INSERT INTO "schema_migrations" (version) VALUES ('20181128140306'), ('20200415130431'), ('20200506135043'), -('20200515050516'); +('20200515050516'), +('20241010140417'), +('20241010140418'); diff --git a/dockerfiles/development/db/init.sh b/dockerfiles/development/db/init.sh new file mode 100644 index 00000000..22326caf --- /dev/null +++ b/dockerfiles/development/db/init.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -e + +pg_restore db.dump -U $POSTGRES_USER -d $POSTGRES_DB --no-owner --no-acl \ No newline at end of file diff --git a/dockerfiles/development/web/Dockerfile b/dockerfiles/development/web/Dockerfile new file mode 100644 index 00000000..3c35d40d --- /dev/null +++ b/dockerfiles/development/web/Dockerfile @@ -0,0 +1,59 @@ +# Use an official Ruby runtime as a parent image +FROM ruby:2.7.8 + +ENV NODE_ENV development +ENV RAILS_ENV development +ENV RACK_ENV development +ENV ROOT_URL 0.0.0.0 +ENV DATABASE_HOST db +ENV DATABASE_PORT 5432 +ENV DATABASE_USER kudo_user +ENV DATABASE_PASSWORD kudos +ENV DATABASE_NAME kudo-o-matic_development +ENV DATABASE_URL postgres://kudo_user:kudos@db:5432 +ENV DATABASE_CLEANER_ALLOW_REMOTE_DATABASE_URL true +ENV REDIS_URL redis://redis:6379/0 +ENV FRONTEND_ROOT_URL http://localhost:9090 +ENV MAIL_USERNAME user@example.com + +# Install Node.js, yarn, Python, and build-essential +RUN apt-get update -qq && \ + apt-get install -y curl && \ + curl -sL https://deb.nodesource.com/setup_20.x | bash - +RUN apt-get install -y \ + gnupg \ + lsb-release \ + nodejs \ + yarnpkg \ + python \ + build-essential + +# Add the PostgreSQL repository GPG key +RUN curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor -o /usr/share/keyrings/postgresql-archive-keyring.gpg + +# Add the PostgreSQL APT repository +RUN echo "deb [signed-by=/usr/share/keyrings/postgresql-archive-keyring.gpg] http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main" | tee /etc/apt/sources.list.d/pgdg.list + +# Install PostgreSQL client version 16 +RUN apt-get update && apt-get install -y postgresql-client-16 + +# Clean up +RUN apt-get clean && rm -rf /var/lib/apt/lists/* + +RUN groupadd -r app && useradd --no-log-init -r -m -g app app + +USER app + +WORKDIR /code + +RUN mkdir /code/vendor + +COPY --chown=app:app Gemfile Gemfile +COPY --chown=app:app Gemfile.lock Gemfile.lock + +# Install the required version of Bundler +RUN gem install bundler -v 2.2.15 + +# Install Ruby dependencies +RUN bundle config set force_ruby_platform true +RUN bundle install diff --git a/spec/graphql/mutations/goal/update_goal.rb b/spec/graphql/mutations/goal/update_goal_spec.rb similarity index 100% rename from spec/graphql/mutations/goal/update_goal.rb rename to spec/graphql/mutations/goal/update_goal_spec.rb diff --git a/spec/graphql/mutations/user/disconnect_slack_spec.rb b/spec/graphql/mutations/user/disconnect_slack_spec.rb index cbc5c25a..0d807d72 100644 --- a/spec/graphql/mutations/user/disconnect_slack_spec.rb +++ b/spec/graphql/mutations/user/disconnect_slack_spec.rb @@ -14,7 +14,7 @@ end let(:mutation_string) do - %( mutation { disconnectSlack() { user { id } } } ) + %( mutation { disconnectSlack { user { id } } } ) end context 'authenticated' do diff --git a/spec/services/slack_service_spec.rb b/spec/services/slack_service_spec.rb index 42f4d646..d32e20e1 100644 --- a/spec/services/slack_service_spec.rb +++ b/spec/services/slack_service_spec.rb @@ -34,8 +34,8 @@ def create_add_post_command(receivers, message, amount) } allow_any_instance_of(Slack::Web::Client).to receive(:oauth_v2_access).and_return(mock_response.as_json) - SlackService.should_receive(:send_welcome_message) - SlackService.should_receive(:join_all_channels) + expect(SlackService).to receive(:send_welcome_message) + expect(SlackService).to receive(:join_all_channels) expect { SlackService.add_to_workspace('token', team.id) @@ -358,7 +358,7 @@ def create_add_post_command(receivers, message, amount) it 'calls the generate base method' do mock_uri = URI::HTTP.build(host: 'fakedomain.com') - SlackService.should_receive(:generate_base_oauth_url).and_return(mock_uri) + expect(SlackService).to receive(:generate_base_oauth_url).and_return(mock_uri) SlackService.get_team_oauth_url('1') end @@ -382,7 +382,7 @@ def create_add_post_command(receivers, message, amount) it 'calls the generate base method' do mock_uri = URI::HTTP.build(host: 'fakedomain.com') - SlackService.should_receive(:generate_base_oauth_url).and_return(mock_uri) + expect(SlackService).to receive(:generate_base_oauth_url).and_return(mock_uri) SlackService.get_user_oauth_url('1') end @@ -403,7 +403,7 @@ def create_add_post_command(receivers, message, amount) reaction: 'unsopperted-emoji' } - SlackService.should_not_receive(:message_is_kudo_o_matic_post?) + expect(SlackService).to_not receive(:message_is_kudo_o_matic_post?) SlackService.reaction_added(team.id, event.as_json) end @@ -501,7 +501,7 @@ def create_add_post_command(receivers, message, amount) reaction: 'unsopperted-emoji' } - SlackService.should_not_receive(:message_is_kudo_o_matic_post?) + expect(SlackService).to_not receive(:message_is_kudo_o_matic_post?) SlackService.reaction_removed(team.id, event.as_json) end