diff --git a/.github/workflows/ruby.yml b/.github/workflows/draft.yml similarity index 96% rename from .github/workflows/ruby.yml rename to .github/workflows/draft.yml index 0aff61e..32ebb1f 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/draft.yml @@ -23,7 +23,6 @@ jobs: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: - ruby-version: '3.2.2' bundler-cache: true - name: Run tests run: bundle exec rake diff --git a/.gitignore b/.gitignore index ad14c46..e0c3069 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,6 @@ yarn-debug.log* /config/credentials/test.key coverage + +/app/assets/builds/* +!/app/assets/builds/.keep diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..057186b --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +ruby 3.3.8 diff --git a/Gemfile b/Gemfile index 8fc2b10..f0e14b2 100644 --- a/Gemfile +++ b/Gemfile @@ -1,37 +1,36 @@ source 'https://rubygems.org' git_source(:github) { |repo| "https://github.com/#{repo}.git" } -ruby '3.2.2' +ruby '3.3.8' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' -gem 'rails', '~> 6.1.7' +gem 'rails', '~> 7.1.0' # Use Puma as the app server gem 'puma', '~> 6.4' # Use SCSS for stylesheets -gem 'sass-rails', '>= 6' +gem 'sassc-rails' # Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker gem 'acts_as_list' gem 'blazer' gem 'cancancan', '~> 3.2' -gem 'devise', '~> 4.7' -gem "importmap-rails", "~> 1.2" +gem 'devise', '~> 4.9' +gem 'importmap-rails', '~> 1.2' gem 'jbuilder', '~> 2.11' gem 'mail', '~> 2.7' gem 'mailerlite' -gem 'rails_admin', '~> 2.2.1' -gem 'redis', '~> 4.7' -gem "stimulus-rails", "~> 1.2" -gem 'turbo-rails', '~> 1.1' +gem 'redis', '~> 5.0' +gem 'stimulus-rails', '~> 1.3' +gem 'turbo-rails', '~> 1.5' -gem 'delayed_job', '~>4.1' -gem 'delayed_job_active_record', '~>4.1' +gem 'delayed_job', '~> 4.1' +gem 'delayed_job_active_record', '~> 4.1' # Use Active Storage variant # gem 'image_processing', '~> 1.2' # Reduces boot times through caching; required in config/boot.rb -gem 'bootsnap', '>= 1.12.0', require: false +gem 'bootsnap', '>= 1.16.0', require: false gem 'net-imap', require: false gem 'net-pop', require: false gem 'net-smtp', require: false @@ -50,15 +49,15 @@ group :development do gem 'guard' gem 'guard-livereload', '~> 2.5', require: false gem 'guard-minitest' - gem 'listen', '~> 3.2' + gem 'listen', '~> 3.8' gem 'rubocop' gem 'solargraph' - gem 'web-console', '>= 3.3.0' + gem 'web-console', '>= 4.2.0' end group :test do # Adds support for Capybara system testing and selenium driver - gem 'capybara', '~> 3.3' + gem 'capybara', '~> 3.39' gem 'selenium-webdriver' # Easy installation and use of web drivers to run system tests with browsers gem 'rails-controller-testing' @@ -67,8 +66,10 @@ group :test do end group :production do - gem 'pg', '1.4.5' + gem 'pg', '~> 1.5' end # Windows does not include zoneinfo files, so bundle the tzinfo-data gem gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby] + +gem 'sprockets-rails' diff --git a/Gemfile.lock b/Gemfile.lock index c6c7ca5..25c9b67 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,142 +1,167 @@ GEM remote: https://rubygems.org/ specs: - actioncable (6.1.7.6) - actionpack (= 6.1.7.6) - activesupport (= 6.1.7.6) + actioncable (7.1.5.1) + actionpack (= 7.1.5.1) + activesupport (= 7.1.5.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) + zeitwerk (~> 2.6) + actionmailbox (7.1.5.1) + actionpack (= 7.1.5.1) + activejob (= 7.1.5.1) + activerecord (= 7.1.5.1) + activestorage (= 7.1.5.1) + activesupport (= 7.1.5.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.1.5.1) + actionpack (= 7.1.5.1) + actionview (= 7.1.5.1) + activejob (= 7.1.5.1) + activesupport (= 7.1.5.1) mail (~> 2.5, >= 2.5.4) - 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) + net-imap + net-pop + net-smtp + rails-dom-testing (~> 2.2) + actionpack (7.1.5.1) + actionview (= 7.1.5.1) + activesupport (= 7.1.5.1) + nokogiri (>= 1.8.5) + racc + rack (>= 2.2.4) + rack-session (>= 1.0.1) rack-test (>= 0.6.3) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.1.7.6) - actionpack (= 6.1.7.6) - activerecord (= 6.1.7.6) - activestorage (= 6.1.7.6) - activesupport (= 6.1.7.6) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + actiontext (7.1.5.1) + actionpack (= 7.1.5.1) + activerecord (= 7.1.5.1) + activestorage (= 7.1.5.1) + activesupport (= 7.1.5.1) + globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (6.1.7.6) - activesupport (= 6.1.7.6) + actionview (7.1.5.1) + activesupport (= 7.1.5.1) builder (~> 3.1) - erubi (~> 1.4) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (6.1.7.6) - activesupport (= 6.1.7.6) + erubi (~> 1.11) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + activejob (7.1.5.1) + activesupport (= 7.1.5.1) globalid (>= 0.3.6) - activemodel (6.1.7.6) - activesupport (= 6.1.7.6) - activemodel-serializers-xml (1.0.2) - activemodel (> 5.x) - activesupport (> 5.x) - builder (~> 3.1) - 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.1.5.1) + activesupport (= 7.1.5.1) + activerecord (7.1.5.1) + activemodel (= 7.1.5.1) + activesupport (= 7.1.5.1) + timeout (>= 0.4.0) + activestorage (7.1.5.1) + actionpack (= 7.1.5.1) + activejob (= 7.1.5.1) + activerecord (= 7.1.5.1) + activesupport (= 7.1.5.1) marcel (~> 1.0) - mini_mime (>= 1.1.0) - activesupport (6.1.7.6) + activesupport (7.1.5.1) + base64 + benchmark (>= 0.3) + bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) + connection_pool (>= 2.2.5) + drb i18n (>= 1.6, < 2) + logger (>= 1.4.2) minitest (>= 5.1) + mutex_m + securerandom (>= 0.3) tzinfo (~> 2.0) - zeitwerk (~> 2.3) - acts_as_list (1.1.0) - activerecord (>= 4.2) - addressable (2.8.5) - public_suffix (>= 2.0.2, < 6.0) - ast (2.4.2) + acts_as_list (1.2.4) + activerecord (>= 6.1) + activesupport (>= 6.1) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + ast (2.4.3) backport (1.2.0) - base64 (0.1.1) - bcrypt (3.1.19) - benchmark (0.2.1) + base64 (0.3.0) + bcrypt (3.1.20) + benchmark (0.4.1) + bigdecimal (3.2.2) bindex (0.8.1) - blazer (2.6.5) - activerecord (>= 5) - chartkick (>= 3.2) - railties (>= 5) - safely_block (>= 0.1.1) - bootsnap (1.16.0) + blazer (3.3.0) + activerecord (>= 7.1) + chartkick (>= 5) + csv + railties (>= 7.1) + safely_block (>= 0.4) + bootsnap (1.18.6) msgpack (~> 1.2) - builder (3.2.4) - byebug (11.1.3) - cancancan (3.5.0) - capybara (3.39.2) + builder (3.3.0) + byebug (12.0.0) + cancancan (3.6.1) + 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) - chartkick (5.0.4) + chartkick (5.1.5) coderay (1.1.3) - concurrent-ruby (1.2.2) + concurrent-ruby (1.3.5) + connection_pool (2.5.3) crass (1.0.6) - date (3.3.3) - delayed_job (4.1.11) - activesupport (>= 3.0, < 8.0) - delayed_job_active_record (4.1.7) - activerecord (>= 3.0, < 8.0) + csv (3.3.5) + date (3.4.1) + delayed_job (4.1.13) + activesupport (>= 3.0, < 9.0) + delayed_job_active_record (4.1.11) + activerecord (>= 3.0, < 9.0) delayed_job (>= 3.0, < 5) - devise (4.9.2) + devise (4.9.4) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 4.1.0) responders warden (~> 1.2.3) - diff-lcs (1.5.0) - docile (1.4.0) - e2mmap (0.1.0) + diff-lcs (1.6.2) + docile (1.4.1) + drb (2.2.3) em-websocket (0.5.3) eventmachine (>= 0.12.9) http_parser.rb (~> 0) - erubi (1.12.0) + erb (5.0.1) + erubi (1.13.1) eventmachine (1.2.7) - faraday (2.7.10) - faraday-net_http (>= 2.0, < 3.1) - ruby2_keywords (>= 0.0.4) + faraday (2.13.1) + faraday-net_http (>= 2.0, < 3.5) + json + logger faraday-follow_redirects (0.3.0) faraday (>= 1, < 3) faraday-mashify (0.1.1) faraday (~> 2.0) hashie - faraday-net_http (3.0.2) - ffi (1.15.5) - foreman (0.87.2) + faraday-net_http (3.4.0) + net-http (>= 0.5.0) + ffi (1.17.2-arm64-darwin) + ffi (1.17.2-x86_64-linux-gnu) + foreman (0.88.1) formatador (1.1.0) globalid (1.2.1) activesupport (>= 6.1) - guard (2.18.0) + guard (2.19.1) formatador (>= 0.2.4) listen (>= 2.7, < 4.0) + logger (~> 1.6) lumberjack (>= 1.0.12, < 2.0) nenv (~> 0.1) notiffany (~> 0.0) + ostruct (~> 0.6) pry (>= 0.13.0) shellany (~> 0.0) thor (>= 0.18.1) @@ -149,186 +174,180 @@ GEM guard-minitest (2.4.6) guard-compat (~> 1.2) minitest (>= 3.0) - haml (5.2.2) - temple (>= 0.8.0) - tilt hashie (5.0.0) - htmlbeautifier (1.4.2) + htmlbeautifier (1.4.3) http_parser.rb (0.8.0) - i18n (1.14.1) + i18n (1.14.7) concurrent-ruby (~> 1.0) - importmap-rails (1.2.1) + importmap-rails (1.2.3) actionpack (>= 6.0.0) + activesupport (>= 6.0.0) railties (>= 6.0.0) - jaro_winkler (1.5.6) - jbuilder (2.11.5) + io-console (0.8.0) + irb (1.15.2) + pp (>= 0.6.0) + rdoc (>= 4.0.0) + reline (>= 0.4.2) + jaro_winkler (1.6.1) + jbuilder (2.13.0) actionview (>= 5.0.0) activesupport (>= 5.0.0) - jquery-rails (4.6.0) - rails-dom-testing (>= 1, < 3) - railties (>= 4.2.0) - thor (>= 0.14, < 2.0) - jquery-ui-rails (6.0.1) - railties (>= 3.2.16) - json (2.6.3) - kaminari (1.2.2) - activesupport (>= 4.1.0) - kaminari-actionview (= 1.2.2) - kaminari-activerecord (= 1.2.2) - kaminari-core (= 1.2.2) - kaminari-actionview (1.2.2) - actionview - kaminari-core (= 1.2.2) - kaminari-activerecord (1.2.2) - activerecord - kaminari-core (= 1.2.2) - kaminari-core (1.2.2) - kramdown (2.4.0) - rexml + json (2.12.2) + kramdown (2.5.1) + rexml (>= 3.3.9) kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) - language_server-protocol (3.17.0.3) - listen (3.8.0) + language_server-protocol (3.17.0.5) + lint_roller (1.1.0) + listen (3.9.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) - loofah (2.21.3) + logger (1.7.0) + loofah (2.24.1) crass (~> 1.0.2) nokogiri (>= 1.12.0) - lumberjack (1.2.9) + lumberjack (1.2.10) mail (2.8.1) mini_mime (>= 0.1.1) net-imap net-pop net-smtp - mailerlite (1.16.0) + mailerlite (1.16.1) faraday (~> 2.0, >= 2.0.1) faraday-follow_redirects (~> 0.3) faraday-mashify (~> 0.1) hashie (>= 4, < 6) - marcel (1.0.2) + marcel (1.0.4) matrix (0.4.2) - method_source (1.0.0) + method_source (1.1.0) mini_mime (1.1.5) - minitest (5.20.0) - msgpack (1.7.2) + minitest (5.25.5) + msgpack (1.8.0) multi_json (1.15.0) + mutex_m (0.3.0) nenv (0.3.0) - nested_form (0.3.2) - net-imap (0.3.7) + net-http (0.6.0) + uri + net-imap (0.5.8) date net-protocol net-pop (0.1.2) net-protocol - net-protocol (0.2.1) + net-protocol (0.2.2) timeout - net-smtp (0.3.3) + net-smtp (0.5.1) net-protocol - nio4r (2.7.0) - nokogiri (1.15.4-arm64-darwin) + nio4r (2.7.4) + nokogiri (1.18.8-arm64-darwin) racc (~> 1.4) - nokogiri (1.15.4-x86_64-linux) + nokogiri (1.18.8-x86_64-linux-gnu) racc (~> 1.4) notiffany (0.1.3) nenv (~> 0.1) shellany (~> 0.0) + observer (0.1.2) orm_adapter (0.5.0) - parallel (1.23.0) - parser (3.2.2.3) + ostruct (0.6.1) + parallel (1.27.0) + parser (3.3.8.0) ast (~> 2.4.1) racc - pg (1.4.5) - pry (0.14.2) + pg (1.5.9) + pp (0.6.2) + prettyprint + prettyprint (0.2.0) + prism (1.4.0) + pry (0.15.2) coderay (~> 1.1) method_source (~> 1.0) - public_suffix (5.0.3) - puma (6.4.2) + psych (5.2.6) + date + stringio + public_suffix (6.0.2) + puma (6.6.0) nio4r (~> 2.0) - racc (1.7.1) - rack (2.2.8) - rack-pjax (1.1.0) - nokogiri (~> 1.5) - rack (>= 1.1) - rack-test (2.1.0) + racc (1.8.1) + rack (3.1.16) + rack-session (2.1.1) + base64 (>= 0.1.0) + rack (>= 3.0.0) + rack-test (2.2.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) + rackup (2.2.1) + rack (>= 3) + rails (7.1.5.1) + actioncable (= 7.1.5.1) + actionmailbox (= 7.1.5.1) + actionmailer (= 7.1.5.1) + actionpack (= 7.1.5.1) + actiontext (= 7.1.5.1) + actionview (= 7.1.5.1) + activejob (= 7.1.5.1) + activemodel (= 7.1.5.1) + activerecord (= 7.1.5.1) + activestorage (= 7.1.5.1) + activesupport (= 7.1.5.1) bundler (>= 1.15.0) - railties (= 6.1.7.6) - sprockets-rails (>= 2.0.0) + railties (= 7.1.5.1) 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) + rails-dom-testing (2.3.0) activesupport (>= 5.0.0) minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.6.0) + rails-html-sanitizer (1.6.2) loofah (~> 2.21) - nokogiri (~> 1.14) - rails_admin (2.2.1) - activemodel-serializers-xml (>= 1.0) - builder (~> 3.1) - haml (>= 4.0, < 6) - jquery-rails (>= 3.0, < 5) - jquery-ui-rails (>= 5.0, < 7) - kaminari (>= 0.14, < 2.0) - nested_form (~> 0.3) - rack-pjax (>= 0.7) - rails (>= 5.0, < 7) - remotipart (~> 1.3) - sassc-rails (>= 1.3, < 3) - railties (6.1.7.6) - actionpack (= 6.1.7.6) - activesupport (= 6.1.7.6) - method_source + nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) + railties (7.1.5.1) + actionpack (= 7.1.5.1) + activesupport (= 7.1.5.1) + irb + rackup (>= 1.0.0) rake (>= 12.2) - thor (~> 1.0) + thor (~> 1.0, >= 1.2.2) + zeitwerk (~> 2.6) rainbow (3.1.1) - rake (13.0.6) + rake (13.3.0) rb-fsevent (0.11.2) - rb-inotify (0.10.1) + rb-inotify (0.11.1) ffi (~> 1.0) - rbs (2.8.4) - redis (4.8.1) - regexp_parser (2.8.1) - remotipart (1.4.4) - responders (3.1.0) + rbs (3.9.4) + logger + rdoc (6.14.0) + erb + psych (>= 4.0.0) + redis (5.4.0) + redis-client (>= 0.22.0) + redis-client (0.24.0) + connection_pool + regexp_parser (2.10.0) + reline (0.6.1) + io-console (~> 0.5) + responders (3.1.1) actionpack (>= 5.2) railties (>= 5.2) - reverse_markdown (2.1.1) + reverse_markdown (3.0.0) nokogiri - rexml (3.2.6) - rubocop (1.56.2) - base64 (~> 0.1.1) + rexml (3.4.1) + rubocop (1.76.1) json (~> 2.3) - language_server-protocol (>= 3.17.0) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) parallel (~> 1.10) - parser (>= 3.2.2.3) + parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 1.8, < 3.0) - rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.28.1, < 2.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.45.0, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.29.0) - parser (>= 3.2.1.0) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.45.1) + parser (>= 3.3.7.2) + prism (~> 1.4) ruby-progressbar (1.13.0) - ruby2_keywords (0.0.5) - rubyzip (2.3.2) - safely_block (0.4.0) - sass-rails (6.0.0) - sassc-rails (~> 2.1, >= 2.1.1) + rubyzip (2.4.1) + safely_block (0.5.0) sassc (2.4.0) ffi (~> 1.9) sassc-rails (2.1.2) @@ -337,7 +356,10 @@ GEM sprockets (> 3.0) sprockets-rails tilt - selenium-webdriver (4.12.0) + securerandom (0.4.1) + selenium-webdriver (4.33.0) + base64 (~> 0.2) + logger (~> 1.4) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) @@ -352,46 +374,53 @@ GEM docile (~> 1.1) simplecov-html (~> 0.11) simplecov_json_formatter (~> 0.1) - simplecov-html (0.12.3) + simplecov-html (0.13.1) simplecov_json_formatter (0.1.4) - solargraph (0.49.0) + solargraph (0.55.1) backport (~> 1.2) - benchmark + benchmark (~> 0.4) bundler (~> 2.0) diff-lcs (~> 1.4) - e2mmap - jaro_winkler (~> 1.5) + jaro_winkler (~> 1.6, >= 1.6.1) kramdown (~> 2.3) kramdown-parser-gfm (~> 1.1) + logger (~> 1.6) + observer (~> 0.1) + ostruct (~> 0.6) parser (~> 3.0) - rbs (~> 2.0) - reverse_markdown (~> 2.0) + rbs (~> 3.3) + reverse_markdown (~> 3.0) rubocop (~> 1.38) thor (~> 1.0) tilt (~> 2.0) yard (~> 0.9, >= 0.9.24) - sprockets (4.2.1) + yard-solargraph (~> 0.1) + sprockets (4.2.2) concurrent-ruby (~> 1.0) + logger rack (>= 2.2.4, < 4) - sprockets-rails (3.4.2) - actionpack (>= 5.2) - activesupport (>= 5.2) + sprockets-rails (3.5.2) + actionpack (>= 6.1) + activesupport (>= 6.1) sprockets (>= 3.0.0) - sqlite3 (1.6.5-arm64-darwin) - sqlite3 (1.6.5-x86_64-linux) - stimulus-rails (1.2.2) + sqlite3 (1.7.3-arm64-darwin) + sqlite3 (1.7.3-x86_64-linux) + stimulus-rails (1.3.4) railties (>= 6.0.0) - temple (0.10.2) - thor (1.2.2) - tilt (2.2.0) - timeout (0.4.0) - turbo-rails (1.4.0) + stringio (3.1.7) + thor (1.3.2) + tilt (2.6.0) + timeout (0.4.3) + turbo-rails (1.5.0) actionpack (>= 6.0.0) activejob (>= 6.0.0) railties (>= 6.0.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - unicode-display_width (2.4.2) + unicode-display_width (3.1.4) + unicode-emoji (~> 4.0, >= 4.0.4) + unicode-emoji (4.0.4) + uri (1.0.3) warden (1.2.9) rack (>= 2.0.9) web-console (4.2.1) @@ -399,29 +428,33 @@ GEM activemodel (>= 6.0.0) bindex (>= 0.4.0) railties (>= 6.0.0) - websocket (1.2.9) - websocket-driver (0.7.6) + websocket (1.2.11) + websocket-driver (0.8.0) + base64 websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) xpath (3.2.0) nokogiri (~> 1.8) - yard (0.9.34) - zeitwerk (2.6.11) + yard (0.9.37) + yard-solargraph (0.1.0) + yard (~> 0.9) + zeitwerk (2.7.3) PLATFORMS arm64-darwin-22 + arm64-darwin-24 x86_64-linux DEPENDENCIES acts_as_list blazer - bootsnap (>= 1.12.0) + bootsnap (>= 1.16.0) byebug cancancan (~> 3.2) - capybara (~> 3.3) + capybara (~> 3.39) delayed_job (~> 4.1) delayed_job_active_record (~> 4.1) - devise (~> 4.7) + devise (~> 4.9) foreman guard guard-livereload (~> 2.5) @@ -429,32 +462,32 @@ DEPENDENCIES htmlbeautifier importmap-rails (~> 1.2) jbuilder (~> 2.11) - listen (~> 3.2) + listen (~> 3.8) mail (~> 2.7) mailerlite net-imap net-pop net-smtp - pg (= 1.4.5) + pg (~> 1.5) puma (~> 6.4) - rails (~> 6.1.7) + rails (~> 7.1.0) rails-controller-testing - rails_admin (~> 2.2.1) - redis (~> 4.7) + redis (~> 5.0) rubocop - sass-rails (>= 6) + sassc-rails selenium-webdriver shoulda (~> 4.0) simplecov solargraph + sprockets-rails sqlite3 (~> 1.4) - stimulus-rails (~> 1.2) - turbo-rails (~> 1.1) + stimulus-rails (~> 1.3) + turbo-rails (~> 1.5) tzinfo-data - web-console (>= 3.3.0) + web-console (>= 4.2.0) RUBY VERSION - ruby 3.2.2p53 + ruby 3.3.8p144 BUNDLED WITH 2.4.19 diff --git a/Procfile.dev b/Procfile.dev deleted file mode 100644 index ee59ea8..0000000 --- a/Procfile.dev +++ /dev/null @@ -1 +0,0 @@ -web: bundle exec rails s \ No newline at end of file diff --git a/app/assets/builds/.keep b/app/assets/builds/.keep new file mode 100644 index 0000000..e69de29 diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 70c3e40..857932b 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -3,8 +3,8 @@ class ApplicationController < ActionController::Base protected - rescue_from CanCan::AccessDenied do |exception| - puts "CanCan::AccessDenied Exception thrown : message=#{exception.message}" + rescue_from CanCan::AccessDenied do |_exception| + redirect_to root_path, flash: { error: 'You are not authorized to perform this action.' } end def check_admin! diff --git a/app/controllers/outline_items_controller.rb b/app/controllers/outline_items_controller.rb index c214c68..9e02e82 100644 --- a/app/controllers/outline_items_controller.rb +++ b/app/controllers/outline_items_controller.rb @@ -1,37 +1,33 @@ +# frozen_string_literal: true + class OutlineItemsController < ApplicationController before_action :story - before_action :outline_item, only: [:show, :update, :destroy] + before_action :outline_item, only: %i[show update destroy] def new - unless can? :create, @story - redirect_to_home - end + redirect_to_home unless can? :create, @story + @outline_item = @outline.outline_items.new end def show - unless can? :read, @story - redirect_to_home - end + redirect_to_home unless can? :read, @story end def create - unless can? :create, @story - redirect_to_home - end + redirect_to_home unless can? :create, @story item_params[:text].each_line do |text| - @outline.outline_items.create(text: text) if text.present? + @outline.outline_items.create(text:) if text.present? end redirect_to story_outline_path(@story), status: :see_other end def update - unless can? :update, @story - redirect_to_home - end - if item_params[:text].empty? + redirect_to_home unless can? :update, @story + + if item_params[:text].to_s.empty? @outline_item.destroy else @outline_item.set_list_position(item_params[:position]) if item_params[:position] @@ -42,26 +38,21 @@ def update end def destroy - unless can? :destroy, @story - redirect_to_home - end + redirect_to_home unless can? :destroy, @story + @outline_item.destroy head :ok end private - def item_params + def item_params params.require(:outline_item).permit(:text, :completed, :position) end def story @story = Story.find(params[:story_id]) - if @story.outline - @outline = @story.outline - else - @outline = Outline.create(story_id: @story.id) - end + @outline = @story.outline || Outline.create(story_id: @story.id) end def outline_item diff --git a/app/controllers/stories_controller.rb b/app/controllers/stories_controller.rb index 90466ee..d72be1e 100644 --- a/app/controllers/stories_controller.rb +++ b/app/controllers/stories_controller.rb @@ -2,11 +2,13 @@ # controller for stories class StoriesController < ApplicationController - load_and_authorize_resource - before_action :stories + load_and_authorize_resource # automatically loads @story and @stories and authorizes them before_action :writing_sessions, only: %i[show update] - def index; end + def index + # filter authorized stories down to only the current user's stories + @stories = @stories.where(user: current_user).order(created_at: :desc) + end def new; end @@ -14,7 +16,11 @@ def create @story = current_user.stories.create(story_params) @writing_sessions = [] - redirect_to story_path(@story) + if @story.valid? + redirect_to story_path(@story) + else + render :new, status: :unprocessable_entity + end end def show @@ -25,9 +31,11 @@ def show def edit; end def update - @story.update(story_params) - - render :show + if @story.update(story_params) + render :show + else + render :edit, status: :unprocessable_entity + end end def destroy @@ -42,10 +50,6 @@ def story_params params.require(:story).permit(:title) end - def stories - @stories = current_user.stories.order(created_at: :desc) - end - def writing_sessions @writing_sessions = @story.writing_sessions.order(updated_at: :desc) end diff --git a/app/models/ability.rb b/app/models/ability.rb index 168f229..8f5c90e 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -6,10 +6,10 @@ class Ability def initialize(user) # Define abilities for the passed in user here. For example: # - user ||= User.new # guest user (not logged in) + return unless user.present? + if user.admin_role? can :manage, :all - can :access, :rails_admin can :access, :blazer end diff --git a/app/models/story.rb b/app/models/story.rb index fd82520..e729b43 100644 --- a/app/models/story.rb +++ b/app/models/story.rb @@ -1,5 +1,5 @@ class Story < ApplicationRecord - # validates_presense_of :title, :user + validates_presence_of :title, :user belongs_to :user has_many :writing_sessions, dependent: :destroy diff --git a/app/views/outline_items/_outline_item.html.erb b/app/views/outline_items/_outline_item.html.erb index 35cd36d..fb8ec98 100644 --- a/app/views/outline_items/_outline_item.html.erb +++ b/app/views/outline_items/_outline_item.html.erb @@ -1,9 +1,9 @@ <% if item.present? %> -
<%= @outline_item.text %>
\ No newline at end of file diff --git a/config/application.rb b/config/application.rb index fbff456..4ebeaa7 100644 --- a/config/application.rb +++ b/config/application.rb @@ -9,7 +9,7 @@ module Web class Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. - config.load_defaults 6.0 + config.load_defaults 7.1 # Settings in config/environments/* take precedence over those specified here. # Application configuration can go into files in config/initializers diff --git a/config/initializers/rails_admin.rb b/config/initializers/rails_admin.rb deleted file mode 100644 index e76b0e6..0000000 --- a/config/initializers/rails_admin.rb +++ /dev/null @@ -1,44 +0,0 @@ -require 'nested_form/engine' -require 'nested_form/builder_mixin' - -RailsAdmin.config do |config| - - ### Popular gems integration - - ## == Devise == - config.authenticate_with do - warden.authenticate! scope: :user - end - config.current_user_method(&:current_user) - - ## == CancanCan == - config.authorize_with :cancancan - - ## == Pundit == - # config.authorize_with :pundit - - ## == PaperTrail == - # config.audit_with :paper_trail, 'User', 'PaperTrail::Version' # PaperTrail >= 3.0.0 - - ### More at https://github.com/sferik/rails_admin/wiki/Base-configuration - - ## == Gravatar integration == - ## To disable Gravatar integration in Navigation Bar set to false - # config.show_gravatar = true - - config.actions do - dashboard # mandatory - index # mandatory - new - export - bulk_delete - show - edit - delete - show_in_app - - ## With an audit adapter, you can add: - # history_index - # history_show - end -end diff --git a/config/routes.rb b/config/routes.rb index e585b04..43ca22a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,5 +1,4 @@ Rails.application.routes.draw do - mount RailsAdmin::Engine => '/admin', as: 'rails_admin' mount Blazer::Engine, at: 'blazer' devise_for :user diff --git a/db/schema.rb b/db/schema.rb index 20d9cef..2c40005 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,14 +10,13 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2022_07_09_194955) do - +ActiveRecord::Schema[7.1].define(version: 2022_07_09_194955) do create_table "blazer_audits", force: :cascade do |t| t.integer "user_id" t.integer "query_id" t.text "statement" t.string "data_source" - t.datetime "created_at" + t.datetime "created_at", precision: nil t.index ["query_id"], name: "index_blazer_audits_on_query_id" t.index ["user_id"], name: "index_blazer_audits_on_user_id" end @@ -31,9 +30,9 @@ t.text "slack_channels" t.string "check_type" t.text "message" - t.datetime "last_run_at" - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false + t.datetime "last_run_at", precision: nil + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.index ["creator_id"], name: "index_blazer_checks_on_creator_id" t.index ["query_id"], name: "index_blazer_checks_on_query_id" end @@ -42,8 +41,8 @@ t.integer "dashboard_id" t.integer "query_id" t.integer "position" - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.index ["dashboard_id"], name: "index_blazer_dashboard_queries_on_dashboard_id" t.index ["query_id"], name: "index_blazer_dashboard_queries_on_query_id" end @@ -51,8 +50,8 @@ create_table "blazer_dashboards", force: :cascade do |t| t.integer "creator_id" t.string "name" - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.index ["creator_id"], name: "index_blazer_dashboards_on_creator_id" end @@ -63,8 +62,8 @@ t.text "statement" t.string "data_source" t.string "status" - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.index ["creator_id"], name: "index_blazer_queries_on_creator_id" end @@ -73,13 +72,13 @@ t.integer "attempts", default: 0, null: false t.text "handler", null: false t.text "last_error" - t.datetime "run_at" - t.datetime "locked_at" - t.datetime "failed_at" + t.datetime "run_at", precision: nil + t.datetime "locked_at", precision: nil + t.datetime "failed_at", precision: nil t.string "locked_by" t.string "queue" - t.datetime "created_at", precision: 6 - t.datetime "updated_at", precision: 6 + t.datetime "created_at" + t.datetime "updated_at" t.index ["priority", "run_at"], name: "delayed_jobs_priority" end @@ -89,24 +88,24 @@ t.boolean "completed", default: false t.integer "position" t.string "timestamps" - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.index ["outline_id"], name: "index_outline_items_on_outline_id" end create_table "outlines", force: :cascade do |t| t.integer "story_id" t.integer "completion" - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.index ["story_id"], name: "index_outlines_on_story_id" end create_table "stories", force: :cascade do |t| t.string "title", null: false t.integer "user_id", null: false - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.index ["user_id"], name: "index_stories_on_user_id" end @@ -114,10 +113,10 @@ t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false t.string "reset_password_token" - t.datetime "reset_password_sent_at" - t.datetime "remember_created_at" - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false + t.datetime "reset_password_sent_at", precision: nil + t.datetime "remember_created_at", precision: nil + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.boolean "admin_role" t.index ["email"], name: "index_users_on_email", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true @@ -126,8 +125,8 @@ create_table "writing_sessions", force: :cascade do |t| t.integer "word_count" t.text "text" - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.integer "user_id" t.integer "story_id", null: false t.index ["story_id"], name: "index_writing_sessions_on_story_id" diff --git a/lib/tasks/test.rake b/lib/tasks/test.rake index d3d1f31..920f137 100644 --- a/lib/tasks/test.rake +++ b/lib/tasks/test.rake @@ -1,9 +1,9 @@ namespace :test do desc 'Run tests with coverage' task :coverage do - require 'simplecov' - SimpleCov.start 'rails' + ENV['SIMPLECOV'] = 'true' + # Run all test types Rake::Task['test'].invoke end end diff --git a/test/controllers/outline_items_controller_test.rb b/test/controllers/outline_items_controller_test.rb index b930058..b29b50a 100644 --- a/test/controllers/outline_items_controller_test.rb +++ b/test/controllers/outline_items_controller_test.rb @@ -1,7 +1,123 @@ -require "test_helper" +require 'test_helper' class OutlineItemsControllerTest < ActionDispatch::IntegrationTest - # test "the truth" do - # assert true - # end + include Devise::Test::IntegrationHelpers + + setup do + @user = users(:bob) + @story = stories(:one) + @outline = outlines(:one) + @outline_item = outline_items(:one) + sign_in @user + end + + test 'should get new' do + get new_story_outline_outline_item_path(@story) + assert_response :success + end + + test 'should not get new if unauthorized' do + sign_out @user + get new_story_outline_outline_item_path(@story) + assert_redirected_to '/user/sign_in' + end + + test 'should show outline item' do + get story_outline_outline_item_path(@story, @outline_item) + assert_response :success + end + + test 'should not show outline item if unauthorized' do + sign_out @user + get story_outline_outline_item_path(@story, @outline_item) + assert_redirected_to '/user/sign_in' + end + + test 'should create outline item' do + assert_difference('OutlineItem.count') do + post story_outline_outline_items_path(@story), params: { + outline_item: { text: 'New outline item' } + } + end + assert_redirected_to story_outline_path(@story) + end + + test 'should create multiple outline items from multiline text' do + assert_difference('OutlineItem.count', 3) do + post story_outline_outline_items_path(@story), params: { + outline_item: { text: "First item\nSecond item\nThird item" } + } + end + assert_redirected_to story_outline_path(@story) + end + + test 'should not create outline item if unauthorized' do + sign_out @user + assert_no_difference('OutlineItem.count') do + post story_outline_outline_items_path(@story), params: { + outline_item: { text: 'New outline item' } + } + end + assert_redirected_to '/user/sign_in' + end + + test 'should update outline item' do + patch story_outline_outline_item_path(@story, @outline_item), params: { + outline_item: { text: 'Updated text' } + } + assert_redirected_to story_outline_path(@story) + @outline_item.reload + assert_equal 'Updated text', @outline_item.text + end + + test 'should update outline item position' do + patch story_outline_outline_item_path(@story, @outline_item), params: { + outline_item: { position: 2, text: @outline_item.text } + } + assert_redirected_to story_outline_path(@story) + @outline_item.reload + assert_equal 2, @outline_item.position + end + + test 'should delete outline item when text is empty' do + assert_difference('OutlineItem.count', -1) do + patch story_outline_outline_item_path(@story, @outline_item), params: { + outline_item: { text: '' } + } + end + assert_redirected_to story_outline_path(@story) + end + + test 'should not update outline item if unauthorized' do + sign_out @user + patch story_outline_outline_item_path(@story, @outline_item), params: { + outline_item: { text: 'Updated text' } + } + assert_redirected_to '/user/sign_in' + @outline_item.reload + assert_not_equal 'Updated text', @outline_item.text + end + + test 'should destroy outline item' do + assert_difference('OutlineItem.count', -1) do + delete story_outline_outline_item_path(@story, @outline_item) + end + assert_response :ok + end + + test 'should not destroy outline item if unauthorized' do + sign_out @user + assert_no_difference('OutlineItem.count') do + delete story_outline_outline_item_path(@story, @outline_item) + end + assert_redirected_to '/user/sign_in' + end + + test 'should create outline if story has none' do + story_without_outline = stories(:two) + assert_difference('Outline.count') do + get new_story_outline_outline_item_path(story_without_outline) + end + assert_response :success + end end diff --git a/test/controllers/stories_controller_test.rb b/test/controllers/stories_controller_test.rb index 8548073..6b375ad 100644 --- a/test/controllers/stories_controller_test.rb +++ b/test/controllers/stories_controller_test.rb @@ -42,18 +42,116 @@ class StoriesControllerTest < ActionDispatch::IntegrationTest assert_response :redirect assert_redirected_to story_url(Story.last) end + + should 'not create story with invalid params' do + assert_no_difference('Story.count') do + post stories_url, params: { story: { title: '' } } + end + assert_response :unprocessable_entity + end end context '#show' do - story = nil - setup do - story = users(:bob).stories.create({ title: 'New Story' }) + @story = users(:bob).stories.create({ title: 'New Story' }) + @writing_sessions = [] + 3.times do + @writing_sessions << @story.writing_sessions.create!(word_count: 100, user: users(:bob)) + end + sign_in users(:bob) end should 'return story' do - get story_url(story.id) + get story_url(@story.id) + assert_response :success + end + + should 'assign writing sessions' do + get story_url(@story.id) + assert_equal @writing_sessions.map(&:id).sort, assigns(:writing_sessions).map(&:id).sort + end + + should 'calculate total word count' do + get story_url(@story.id) + assert_equal 300, assigns(:word_count_total) + end + + should 'assign next outline item' do + outline = Outline.create!(story: @story) + outline_item = outline.outline_items.create!(text: 'Test Item') + get story_url(@story.id) + assert_equal outline_item, assigns(:next_outline_item) + end + + should 'prevent access to other users stories' do + other_story = users(:admin).stories.create({ title: 'Other Story' }) + get story_url(other_story.id) + assert_redirected_to root_path + end + end + + context '#edit' do + setup do + @story = users(:bob).stories.create({ title: 'New Story' }) + end + + should 'get edit form' do + get edit_story_url(@story.id) assert_response :success end + + should 'prevent editing other users stories' do + other_story = users(:admin).stories.create({ title: 'Other Story' }) + get edit_story_url(other_story.id) + assert_redirected_to root_path + end + end + + context '#update' do + setup do + @story = users(:bob).stories.create({ title: 'New Story' }) + end + + should 'update story with valid params' do + patch story_url(@story.id), params: { story: { title: 'Updated Title' } } + @story.reload + assert_equal 'Updated Title', @story.title + assert_response :success + end + + should 'not update story with invalid params' do + original_title = @story.title + patch story_url(@story.id), params: { story: { title: '' } } + @story.reload + assert_equal original_title, @story.title + assert_response :unprocessable_entity + end + + should 'prevent updating other users stories' do + other_story = users(:admin).stories.create({ title: 'Other Story' }) + patch story_url(other_story.id), params: { story: { title: 'Updated Title' } } + assert_redirected_to root_path + end + end + + context '#destroy' do + setup do + @story = users(:bob).stories.create({ title: 'New Story' }) + end + + should 'destroy story' do + assert_difference('Story.count', -1) do + delete story_url(@story.id) + end + assert_redirected_to stories_url + end + + should 'prevent destroying other users stories' do + other_story = users(:admin).stories.create({ title: 'Other Story' }) + assert_no_difference('Story.count') do + delete story_url(other_story.id) + end + assert_redirected_to root_path + end end end diff --git a/test/fixtures/outline_items.yml b/test/fixtures/outline_items.yml index ecc6831..99cc9d8 100644 --- a/test/fixtures/outline_items.yml +++ b/test/fixtures/outline_items.yml @@ -17,6 +17,6 @@ two: # column: value three: - outline: two - text: "chapter 1" - position: 1 + outline: one + text: "chapter 3" + position: 3 diff --git a/test/fixtures/outlines.yml b/test/fixtures/outlines.yml index b9a8d4f..38d499d 100644 --- a/test/fixtures/outlines.yml +++ b/test/fixtures/outlines.yml @@ -7,5 +7,4 @@ one: story: one -two: - story: two +# Note: story two intentionally has no outline to test outline creation diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml index 555ea1d..1afba43 100644 --- a/test/fixtures/users.yml +++ b/test/fixtures/users.yml @@ -7,11 +7,14 @@ bob: email: test@email.com admin_role: false + encrypted_password: <%= User.new.send(:password_digest, 'password123') %> jane: email: jane@email.com admin_role: false + encrypted_password: <%= User.new.send(:password_digest, 'password123') %> admin: email: admin@email.com admin_role: true + encrypted_password: <%= User.new.send(:password_digest, 'password123') %> diff --git a/test/test_helper.rb b/test/test_helper.rb index 37934eb..625f290 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,5 +1,15 @@ # frozen_string_literal: true +if ENV['SIMPLECOV'] + require 'simplecov' + + SimpleCov.start 'rails' do + add_group 'Components', 'app/components' + add_group 'Services', 'app/services' + add_filter 'lib/generators' + end +end + ENV['RAILS_ENV'] ||= 'test' require_relative '../config/environment' require 'rails/test_help' @@ -9,6 +19,16 @@ class TestCase # Run tests in parallel with specified workers parallelize(workers: :number_of_processors) + if ENV['SIMPLECOV'] + parallelize_setup do |worker| + SimpleCov.command_name "#{SimpleCov.command_name}-#{worker}" + end + + parallelize_teardown do + SimpleCov.result + end + end + # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. fixtures :all