diff --git a/Gemfile b/Gemfile index 5a8ffc43..0a51473a 100644 --- a/Gemfile +++ b/Gemfile @@ -2,21 +2,38 @@ source 'https://rubygems.org' ruby File.read('.ruby-version').chomp -gem 'byebug', platforms: [:mri, :mingw, :x64_mingw], group: [:development, :test] -gem 'capybara', group: [:development, :test] gem 'coffee-rails' gem 'devise' gem 'jbuilder' -gem 'listen', group: :development gem 'pg' gem 'pry-rails' gem 'puma' gem 'rails', '~> 7.0.3' gem 'rspec-rails' gem 'sass-rails' -gem 'selenium-webdriver', group: [:development, :test] -gem 'spring', group: :development gem 'turbolinks' gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] gem 'uglifier' -gem 'web-console', group: :development +gem 'sidekiq' +gem 'sidekiq-scheduler', '~> 4.0' +gem 'redis' +gem 'kaminari' + + + +group :development do + gem 'foreman', group: :development + + gem 'spring' + gem 'web-console' + gem 'listen' + gem 'rubocop', require: false + +end + +group :development, :test do + gem 'selenium-webdriver' + gem 'pry' + gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] + gem 'capybara' +end \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index 14ec6457..90270b0f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,87 +1,88 @@ GEM remote: https://rubygems.org/ specs: - actioncable (7.0.4) - actionpack (= 7.0.4) - activesupport (= 7.0.4) + actioncable (7.0.8.4) + actionpack (= 7.0.8.4) + activesupport (= 7.0.8.4) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (7.0.4) - actionpack (= 7.0.4) - activejob (= 7.0.4) - activerecord (= 7.0.4) - activestorage (= 7.0.4) - activesupport (= 7.0.4) + actionmailbox (7.0.8.4) + actionpack (= 7.0.8.4) + activejob (= 7.0.8.4) + activerecord (= 7.0.8.4) + activestorage (= 7.0.8.4) + activesupport (= 7.0.8.4) mail (>= 2.7.1) net-imap net-pop net-smtp - actionmailer (7.0.4) - actionpack (= 7.0.4) - actionview (= 7.0.4) - activejob (= 7.0.4) - activesupport (= 7.0.4) + actionmailer (7.0.8.4) + actionpack (= 7.0.8.4) + actionview (= 7.0.8.4) + activejob (= 7.0.8.4) + activesupport (= 7.0.8.4) mail (~> 2.5, >= 2.5.4) net-imap net-pop net-smtp rails-dom-testing (~> 2.0) - actionpack (7.0.4) - actionview (= 7.0.4) - activesupport (= 7.0.4) - rack (~> 2.0, >= 2.2.0) + actionpack (7.0.8.4) + actionview (= 7.0.8.4) + activesupport (= 7.0.8.4) + rack (~> 2.0, >= 2.2.4) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (7.0.4) - actionpack (= 7.0.4) - activerecord (= 7.0.4) - activestorage (= 7.0.4) - activesupport (= 7.0.4) + actiontext (7.0.8.4) + actionpack (= 7.0.8.4) + activerecord (= 7.0.8.4) + activestorage (= 7.0.8.4) + activesupport (= 7.0.8.4) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.0.4) - activesupport (= 7.0.4) + actionview (7.0.8.4) + activesupport (= 7.0.8.4) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (7.0.4) - activesupport (= 7.0.4) + activejob (7.0.8.4) + activesupport (= 7.0.8.4) globalid (>= 0.3.6) - activemodel (7.0.4) - activesupport (= 7.0.4) - activerecord (7.0.4) - activemodel (= 7.0.4) - activesupport (= 7.0.4) - activestorage (7.0.4) - actionpack (= 7.0.4) - activejob (= 7.0.4) - activerecord (= 7.0.4) - activesupport (= 7.0.4) + activemodel (7.0.8.4) + activesupport (= 7.0.8.4) + activerecord (7.0.8.4) + activemodel (= 7.0.8.4) + activesupport (= 7.0.8.4) + activestorage (7.0.8.4) + actionpack (= 7.0.8.4) + activejob (= 7.0.8.4) + activerecord (= 7.0.8.4) + activesupport (= 7.0.8.4) marcel (~> 1.0) mini_mime (>= 1.1.0) - activesupport (7.0.4) + activesupport (7.0.8.4) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) - addressable (2.8.1) - public_suffix (>= 2.0.2, < 6.0) - bcrypt (3.1.18) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + ast (2.4.2) + base64 (0.2.0) + bcrypt (3.1.20) bindex (0.8.1) - builder (3.2.4) + builder (3.3.0) byebug (11.1.3) - capybara (3.37.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) - childprocess (4.1.0) coderay (1.1.3) coffee-rails (5.0.0) coffee-script (>= 2.2.0) @@ -90,125 +91,169 @@ GEM coffee-script-source execjs coffee-script-source (1.12.2) - concurrent-ruby (1.1.10) + concurrent-ruby (1.3.3) + connection_pool (2.4.1) crass (1.0.6) - devise (4.8.1) + date (3.3.4) + 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) - digest (3.1.0) - erubi (1.11.0) - execjs (2.8.1) - ffi (1.15.5) - globalid (1.0.0) - activesupport (>= 5.0) - i18n (1.12.0) + diff-lcs (1.5.1) + erubi (1.13.0) + et-orbi (1.2.11) + tzinfo + execjs (2.9.1) + ffi (1.17.0-x86_64-darwin) + foreman (0.88.1) + fugit (1.11.0) + et-orbi (~> 1, >= 1.2.11) + raabro (~> 1.4) + globalid (1.2.1) + activesupport (>= 6.1) + i18n (1.14.5) concurrent-ruby (~> 1.0) - jbuilder (2.11.5) + jbuilder (2.12.0) actionview (>= 5.0.0) activesupport (>= 5.0.0) - listen (3.7.1) + json (2.7.2) + 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) + language_server-protocol (3.17.0.3) + listen (3.9.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) - loofah (2.19.0) + logger (1.6.0) + loofah (2.22.0) crass (~> 1.0.2) - nokogiri (>= 1.5.9) - mail (2.7.1) + nokogiri (>= 1.12.0) + mail (2.8.1) mini_mime (>= 0.1.1) - marcel (1.0.2) + net-imap + net-pop + net-smtp + marcel (1.0.4) matrix (0.4.2) - method_source (1.0.0) - mini_mime (1.1.2) - mini_portile2 (2.8.0) - minitest (5.16.3) - net-imap (0.2.3) - digest + method_source (1.1.0) + mini_mime (1.1.5) + minitest (5.24.1) + net-imap (0.4.14) + date net-protocol - strscan - net-pop (0.1.1) - digest + net-pop (0.1.2) net-protocol + net-protocol (0.2.2) timeout - net-protocol (0.1.3) - timeout - net-smtp (0.3.1) - digest + net-smtp (0.5.0) net-protocol - timeout - nio4r (2.5.8) - nokogiri (1.13.8) - mini_portile2 (~> 2.8.0) + nio4r (2.7.3) + nokogiri (1.16.7-x86_64-darwin) racc (~> 1.4) orm_adapter (0.5.0) - pg (1.4.3) - pry (0.14.1) + parallel (1.26.2) + parser (3.3.4.2) + ast (~> 2.4.1) + racc + pg (1.5.7) + pry (0.14.2) coderay (~> 1.1) method_source (~> 1.0) - pry-rails (0.3.9) - pry (>= 0.10.4) - public_suffix (5.0.0) - puma (5.6.5) + pry-rails (0.3.11) + pry (>= 0.13.0) + public_suffix (6.0.1) + puma (6.4.2) nio4r (~> 2.0) - racc (1.6.0) - rack (2.2.4) - rack-test (2.0.2) + raabro (1.4.0) + racc (1.8.1) + rack (2.2.9) + rack-test (2.1.0) rack (>= 1.3) - rails (7.0.4) - actioncable (= 7.0.4) - actionmailbox (= 7.0.4) - actionmailer (= 7.0.4) - actionpack (= 7.0.4) - actiontext (= 7.0.4) - actionview (= 7.0.4) - activejob (= 7.0.4) - activemodel (= 7.0.4) - activerecord (= 7.0.4) - activestorage (= 7.0.4) - activesupport (= 7.0.4) + rails (7.0.8.4) + actioncable (= 7.0.8.4) + actionmailbox (= 7.0.8.4) + actionmailer (= 7.0.8.4) + actionpack (= 7.0.8.4) + actiontext (= 7.0.8.4) + actionview (= 7.0.8.4) + activejob (= 7.0.8.4) + activemodel (= 7.0.8.4) + activerecord (= 7.0.8.4) + activestorage (= 7.0.8.4) + activesupport (= 7.0.8.4) bundler (>= 1.15.0) - railties (= 7.0.4) - rails-dom-testing (2.0.3) - activesupport (>= 4.2.0) + railties (= 7.0.8.4) + rails-dom-testing (2.2.0) + activesupport (>= 5.0.0) + minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.4.3) - loofah (~> 2.3) - railties (7.0.4) - actionpack (= 7.0.4) - activesupport (= 7.0.4) + rails-html-sanitizer (1.6.0) + loofah (~> 2.21) + nokogiri (~> 1.14) + railties (7.0.8.4) + actionpack (= 7.0.8.4) + activesupport (= 7.0.8.4) method_source rake (>= 12.2) thor (~> 1.0) zeitwerk (~> 2.5) - rake (13.0.6) + rainbow (3.1.1) + rake (13.2.1) rb-fsevent (0.11.2) - rb-inotify (0.10.1) + rb-inotify (0.11.1) ffi (~> 1.0) - regexp_parser (2.5.0) - responders (3.0.1) - actionpack (>= 5.0) - railties (>= 5.0) - rexml (3.2.5) - rspec-core (3.11.0) - rspec-support (~> 3.11.0) - rspec-expectations (3.11.1) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.11.0) - rspec-mocks (3.11.1) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.11.0) - rspec-rails (5.1.2) + redis (4.8.1) + regexp_parser (2.9.2) + responders (3.1.1) actionpack (>= 5.2) - activesupport (>= 5.2) railties (>= 5.2) - rspec-core (~> 3.10) - rspec-expectations (~> 3.10) - rspec-mocks (~> 3.10) - rspec-support (~> 3.10) - rspec-support (3.11.1) + rexml (3.3.4) + strscan + rspec-core (3.13.0) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.1) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.1) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-rails (6.1.3) + actionpack (>= 6.1) + activesupport (>= 6.1) + railties (>= 6.1) + rspec-core (~> 3.13) + rspec-expectations (~> 3.13) + rspec-mocks (~> 3.13) + rspec-support (~> 3.13) + rspec-support (3.13.1) + rubocop (1.65.1) + json (~> 2.3) + language_server-protocol (>= 3.17.0) + parallel (~> 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.4, < 3.0) + rexml (>= 3.2.5, < 4.0) + rubocop-ast (>= 1.31.1, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 3.0) + rubocop-ast (1.32.0) + parser (>= 3.3.1.0) + ruby-progressbar (1.13.0) rubyzip (2.3.2) + rufus-scheduler (3.9.1) + fugit (~> 1.1, >= 1.1.6) sass-rails (6.0.0) sassc-rails (~> 2.1, >= 2.1.1) sassc (2.4.0) @@ -219,62 +264,80 @@ GEM sprockets (> 3.0) sprockets-rails tilt - selenium-webdriver (4.4.0) - childprocess (>= 0.5, < 5.0) + selenium-webdriver (4.23.0) + base64 (~> 0.2) + logger (~> 1.4) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) - spring (4.1.0) - sprockets (4.1.1) + sidekiq (6.5.12) + connection_pool (>= 2.2.5, < 3) + rack (~> 2.0) + redis (>= 4.5.0, < 5) + sidekiq-scheduler (4.0.3) + redis (>= 4.2.0) + rufus-scheduler (~> 3.2) + sidekiq (>= 4, < 7) + tilt (>= 1.4.0) + spring (4.2.1) + sprockets (4.2.1) concurrent-ruby (~> 1.0) - rack (> 1, < 3) - sprockets-rails (3.4.2) - actionpack (>= 5.2) - activesupport (>= 5.2) + rack (>= 2.2.4, < 4) + sprockets-rails (3.5.2) + actionpack (>= 6.1) + activesupport (>= 6.1) sprockets (>= 3.0.0) - strscan (3.0.4) - thor (1.2.1) - tilt (2.0.11) - timeout (0.3.0) + strscan (3.1.0) + thor (1.3.1) + tilt (2.4.0) + timeout (0.4.1) turbolinks (5.2.1) turbolinks-source (~> 5.2) turbolinks-source (5.2.0) - tzinfo (2.0.5) + tzinfo (2.0.6) concurrent-ruby (~> 1.0) uglifier (4.2.0) execjs (>= 0.3.0, < 3) + unicode-display_width (2.5.0) warden (1.2.9) rack (>= 2.0.9) - web-console (4.2.0) + web-console (4.2.1) actionview (>= 6.0.0) activemodel (>= 6.0.0) bindex (>= 0.4.0) railties (>= 6.0.0) - websocket (1.2.9) - websocket-driver (0.7.5) + websocket (1.2.11) + websocket-driver (0.7.6) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.6.0) + zeitwerk (2.6.17) PLATFORMS - ruby + x86_64-darwin-23 DEPENDENCIES byebug capybara coffee-rails devise + foreman jbuilder + kaminari listen pg + pry pry-rails puma rails (~> 7.0.3) + redis rspec-rails + rubocop sass-rails selenium-webdriver + sidekiq + sidekiq-scheduler (~> 4.0) spring turbolinks tzinfo-data diff --git a/Gemfile.lock.old b/Gemfile.lock.old new file mode 100644 index 00000000..14ec6457 --- /dev/null +++ b/Gemfile.lock.old @@ -0,0 +1,288 @@ +GEM + remote: https://rubygems.org/ + specs: + actioncable (7.0.4) + actionpack (= 7.0.4) + activesupport (= 7.0.4) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + actionmailbox (7.0.4) + actionpack (= 7.0.4) + activejob (= 7.0.4) + activerecord (= 7.0.4) + activestorage (= 7.0.4) + activesupport (= 7.0.4) + mail (>= 2.7.1) + net-imap + net-pop + net-smtp + actionmailer (7.0.4) + actionpack (= 7.0.4) + actionview (= 7.0.4) + activejob (= 7.0.4) + activesupport (= 7.0.4) + mail (~> 2.5, >= 2.5.4) + net-imap + net-pop + net-smtp + rails-dom-testing (~> 2.0) + actionpack (7.0.4) + actionview (= 7.0.4) + activesupport (= 7.0.4) + rack (~> 2.0, >= 2.2.0) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.2.0) + actiontext (7.0.4) + actionpack (= 7.0.4) + activerecord (= 7.0.4) + activestorage (= 7.0.4) + activesupport (= 7.0.4) + globalid (>= 0.6.0) + nokogiri (>= 1.8.5) + actionview (7.0.4) + activesupport (= 7.0.4) + builder (~> 3.1) + erubi (~> 1.4) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.1, >= 1.2.0) + activejob (7.0.4) + activesupport (= 7.0.4) + globalid (>= 0.3.6) + activemodel (7.0.4) + activesupport (= 7.0.4) + activerecord (7.0.4) + activemodel (= 7.0.4) + activesupport (= 7.0.4) + activestorage (7.0.4) + actionpack (= 7.0.4) + activejob (= 7.0.4) + activerecord (= 7.0.4) + activesupport (= 7.0.4) + marcel (~> 1.0) + mini_mime (>= 1.1.0) + activesupport (7.0.4) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 1.6, < 2) + minitest (>= 5.1) + tzinfo (~> 2.0) + addressable (2.8.1) + public_suffix (>= 2.0.2, < 6.0) + bcrypt (3.1.18) + bindex (0.8.1) + builder (3.2.4) + byebug (11.1.3) + capybara (3.37.1) + addressable + matrix + mini_mime (>= 0.1.3) + nokogiri (~> 1.8) + rack (>= 1.6.0) + rack-test (>= 0.6.3) + regexp_parser (>= 1.5, < 3.0) + xpath (~> 3.2) + childprocess (4.1.0) + coderay (1.1.3) + coffee-rails (5.0.0) + coffee-script (>= 2.2.0) + railties (>= 5.2.0) + coffee-script (2.4.1) + coffee-script-source + execjs + coffee-script-source (1.12.2) + concurrent-ruby (1.1.10) + crass (1.0.6) + devise (4.8.1) + bcrypt (~> 3.0) + orm_adapter (~> 0.1) + railties (>= 4.1.0) + responders + warden (~> 1.2.3) + diff-lcs (1.5.0) + digest (3.1.0) + erubi (1.11.0) + execjs (2.8.1) + ffi (1.15.5) + globalid (1.0.0) + activesupport (>= 5.0) + i18n (1.12.0) + concurrent-ruby (~> 1.0) + jbuilder (2.11.5) + actionview (>= 5.0.0) + activesupport (>= 5.0.0) + listen (3.7.1) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + loofah (2.19.0) + crass (~> 1.0.2) + nokogiri (>= 1.5.9) + mail (2.7.1) + mini_mime (>= 0.1.1) + marcel (1.0.2) + matrix (0.4.2) + method_source (1.0.0) + mini_mime (1.1.2) + mini_portile2 (2.8.0) + minitest (5.16.3) + net-imap (0.2.3) + digest + net-protocol + strscan + net-pop (0.1.1) + digest + net-protocol + timeout + net-protocol (0.1.3) + timeout + net-smtp (0.3.1) + digest + net-protocol + timeout + nio4r (2.5.8) + nokogiri (1.13.8) + mini_portile2 (~> 2.8.0) + racc (~> 1.4) + orm_adapter (0.5.0) + pg (1.4.3) + pry (0.14.1) + coderay (~> 1.1) + method_source (~> 1.0) + pry-rails (0.3.9) + pry (>= 0.10.4) + public_suffix (5.0.0) + puma (5.6.5) + nio4r (~> 2.0) + racc (1.6.0) + rack (2.2.4) + rack-test (2.0.2) + rack (>= 1.3) + rails (7.0.4) + actioncable (= 7.0.4) + actionmailbox (= 7.0.4) + actionmailer (= 7.0.4) + actionpack (= 7.0.4) + actiontext (= 7.0.4) + actionview (= 7.0.4) + activejob (= 7.0.4) + activemodel (= 7.0.4) + activerecord (= 7.0.4) + activestorage (= 7.0.4) + activesupport (= 7.0.4) + bundler (>= 1.15.0) + railties (= 7.0.4) + rails-dom-testing (2.0.3) + activesupport (>= 4.2.0) + nokogiri (>= 1.6) + rails-html-sanitizer (1.4.3) + loofah (~> 2.3) + railties (7.0.4) + actionpack (= 7.0.4) + activesupport (= 7.0.4) + method_source + rake (>= 12.2) + thor (~> 1.0) + zeitwerk (~> 2.5) + rake (13.0.6) + rb-fsevent (0.11.2) + rb-inotify (0.10.1) + ffi (~> 1.0) + regexp_parser (2.5.0) + responders (3.0.1) + actionpack (>= 5.0) + railties (>= 5.0) + rexml (3.2.5) + rspec-core (3.11.0) + rspec-support (~> 3.11.0) + rspec-expectations (3.11.1) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.11.0) + rspec-mocks (3.11.1) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.11.0) + rspec-rails (5.1.2) + actionpack (>= 5.2) + activesupport (>= 5.2) + railties (>= 5.2) + rspec-core (~> 3.10) + rspec-expectations (~> 3.10) + rspec-mocks (~> 3.10) + rspec-support (~> 3.10) + rspec-support (3.11.1) + rubyzip (2.3.2) + sass-rails (6.0.0) + sassc-rails (~> 2.1, >= 2.1.1) + sassc (2.4.0) + ffi (~> 1.9) + sassc-rails (2.1.2) + railties (>= 4.0.0) + sassc (>= 2.0) + sprockets (> 3.0) + sprockets-rails + tilt + selenium-webdriver (4.4.0) + childprocess (>= 0.5, < 5.0) + rexml (~> 3.2, >= 3.2.5) + rubyzip (>= 1.2.2, < 3.0) + websocket (~> 1.0) + spring (4.1.0) + sprockets (4.1.1) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) + sprockets-rails (3.4.2) + actionpack (>= 5.2) + activesupport (>= 5.2) + sprockets (>= 3.0.0) + strscan (3.0.4) + thor (1.2.1) + tilt (2.0.11) + timeout (0.3.0) + turbolinks (5.2.1) + turbolinks-source (~> 5.2) + turbolinks-source (5.2.0) + tzinfo (2.0.5) + concurrent-ruby (~> 1.0) + uglifier (4.2.0) + execjs (>= 0.3.0, < 3) + warden (1.2.9) + rack (>= 2.0.9) + web-console (4.2.0) + actionview (>= 6.0.0) + activemodel (>= 6.0.0) + bindex (>= 0.4.0) + railties (>= 6.0.0) + websocket (1.2.9) + websocket-driver (0.7.5) + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.5) + xpath (3.2.0) + nokogiri (~> 1.8) + zeitwerk (2.6.0) + +PLATFORMS + ruby + +DEPENDENCIES + byebug + capybara + coffee-rails + devise + jbuilder + listen + pg + pry-rails + puma + rails (~> 7.0.3) + rspec-rails + sass-rails + selenium-webdriver + spring + turbolinks + tzinfo-data + uglifier + web-console + +RUBY VERSION + ruby 3.1.2p20 + +BUNDLED WITH + 2.3.22 diff --git a/Procfile b/Procfile new file mode 100644 index 00000000..e84acd74 --- /dev/null +++ b/Procfile @@ -0,0 +1,2 @@ +web: bin/rails server -p 3000 +worker: bundle exec sidekiq \ No newline at end of file diff --git a/app/controllers/news_details_controller.rb b/app/controllers/news_details_controller.rb new file mode 100644 index 00000000..fc661c0a --- /dev/null +++ b/app/controllers/news_details_controller.rb @@ -0,0 +1,28 @@ +class NewsDetailsController < ApplicationController + before_action :authenticate_user! + def index + @news_details = NewsDetail.includes(:users).page(params[:page]) + end + + def liked_index + @news_details = NewsDetail.joins(:users).distinct.page(params[:page]) + end + + def upvote + @news_detail = NewsDetail.find(params[:id]) + @news_detail.users << current_user + + respond_to do |format| + format.html { redirect_to news_details_path } + end + end + + def downvote + @news_detail = NewsDetail.find(params[:id]) + @news_detail.users.delete(current_user) + + respond_to do |format| + format.html { redirect_to news_details_path } + end + end +end diff --git a/app/controllers/pages_controller.rb b/app/controllers/pages_controller.rb index ce3bf586..c2cef141 100644 --- a/app/controllers/pages_controller.rb +++ b/app/controllers/pages_controller.rb @@ -1,2 +1,5 @@ class PagesController < ApplicationController + def index + ## not used + end end diff --git a/app/models/news_detail.rb b/app/models/news_detail.rb new file mode 100644 index 00000000..6a00b93b --- /dev/null +++ b/app/models/news_detail.rb @@ -0,0 +1,7 @@ +class NewsDetail < ApplicationRecord + scope :most_recent_story, -> { order(hn_id: :desc).limit(1).first } + + validates :hn_id, uniqueness: true + + has_and_belongs_to_many :users +end diff --git a/app/models/user.rb b/app/models/user.rb index b2091f9a..6e6c299e 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -3,4 +3,6 @@ class User < ApplicationRecord # :confirmable, :lockable, :timeoutable and :omniauthable devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable + + has_and_belongs_to_many :news_details end diff --git a/app/services/hacker_news/add_new_stories.rb b/app/services/hacker_news/add_new_stories.rb new file mode 100644 index 00000000..61f2ff74 --- /dev/null +++ b/app/services/hacker_news/add_new_stories.rb @@ -0,0 +1,20 @@ +module HackerNews + class AddNewStories < Base + attr_reader :story_ids + + def initialize(story_ids) + @story_ids = story_ids + end + + def call + story_ids.each do |story_id| + next if NewsDetail.find_by(hn_id: story_id) + + @result = HackerNews::CreateItemDetail.new(story_id).call + break unless @result.success? + end + + @result || Result.new(false, nil, 'No new stories added') + end + end +end diff --git a/app/services/hacker_news/base.rb b/app/services/hacker_news/base.rb new file mode 100644 index 00000000..4535d6e8 --- /dev/null +++ b/app/services/hacker_news/base.rb @@ -0,0 +1,22 @@ +module HackerNews + class Base + Result = Struct.new(:success?, :data, :errors) + + def get_new_stories + url = URI.parse('https://hacker-news.firebaseio.com/v0/newstories.json') + + http = Net::HTTP.new(url.host, url.port) + http.use_ssl = (url.scheme == 'https') + request = Net::HTTP::Get.new(url) + request['Content-Type'] = 'application/json' + http.request(request) + end + + def newest_story_id + @_uri ||= begin + uri = URI('https://hacker-news.firebaseio.com/v0/maxitem.json') + Net::HTTP.get(uri).gsub(/\s+/, '').to_i + end + end + end +end diff --git a/app/services/hacker_news/create_item_detail.rb b/app/services/hacker_news/create_item_detail.rb new file mode 100644 index 00000000..d8fbdde5 --- /dev/null +++ b/app/services/hacker_news/create_item_detail.rb @@ -0,0 +1,44 @@ +module HackerNews + class CreateItemDetail < Base + attr_reader :story_id + + def initialize(story_id) + @story_id = story_id + end + + def call + data = get_story_detail(story_id) + create_story_detail(data) + end + + private + + def get_story_detail(story_id) + url = URI.parse("https://hacker-news.firebaseio.com/v0/item/#{story_id}.json?") + http = Net::HTTP.new(url.host, url.port) + http.use_ssl = (url.scheme == 'https') + + request = Net::HTTP::Get.new(url) + request['Content-Type'] = 'application/json' + + http.request(request).body + end + + def create_story_detail(data) + data = JSON.parse(data) + if data.present? + @news_detail = NewsDetail.new(hn_id: data['id'], url: data['url'], + author: data['by'], score: data['score'], title: data['title'], + story_type: data['type'], comment_count: data['descendants']) + + if @news_detail.save + Result.new(true, nil, '') + else + Result.new(false, data['hn_id'], 'failed saving HN details') + end + else + Result.new(false, nil, 'failed saving HN details') + end + end + end +end diff --git a/app/services/hacker_news/get_story_ids.rb b/app/services/hacker_news/get_story_ids.rb new file mode 100644 index 00000000..093ab39c --- /dev/null +++ b/app/services/hacker_news/get_story_ids.rb @@ -0,0 +1,17 @@ +module HackerNews + class GetStoryIds < Base + def call + response = get_new_stories + + if response.class.name == 'Net::HTTPOK' + ## get news item details. + + story_ids = get_new_stories + story_ids = JSON.parse(response.body) + Result.new(true, story_ids, '') + else + Result.new(false, nil, 'Request to HN failed') + end + end + end +end diff --git a/app/sidekiq/poll_hacker_news_job.rb b/app/sidekiq/poll_hacker_news_job.rb new file mode 100644 index 00000000..d908fc89 --- /dev/null +++ b/app/sidekiq/poll_hacker_news_job.rb @@ -0,0 +1,28 @@ +class PollHackerNewsJob + include Sidekiq::Job + + def perform(*_args) + puts 'polling Hacker News' + ## If no news deatilas exist create intial set of them + + if NewsDetail.count == 0 + result = HackerNews::GetStoryIds.new.call + result.data.each do |story_id| + HackerNews::CreateItemDetail.new(story_id).call + end + end + most_recent_story = NewsDetail.most_recent_story.try(:hn_id) || 0 + if most_recent_story < HackerNews::Base.new.newest_story_id + puts 'adding new story' + + story_id_result = HackerNews::GetStoryIds.new.call + + story_ids = story_id_result.data + result = HackerNews::AddNewStories.new(story_ids).call + + puts "#{result.success? ? ' New stories added' : result.errors}" + else + puts 'No new stories' + end + end +end diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb new file mode 100644 index 00000000..819ae5b8 --- /dev/null +++ b/app/views/devise/sessions/new.html.erb @@ -0,0 +1,17 @@ +

Log in

+ +<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %> +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email" %> +
+ +
+ <%= f.label :password %>
+ <%= f.password_field :password, autocomplete: "current-password" %> +
+ +
+ <%= f.submit "Log in" %> +
+<% end %> diff --git a/app/views/devise/shared/_error_messages.html.erb b/app/views/devise/shared/_error_messages.html.erb new file mode 100644 index 00000000..cabfe307 --- /dev/null +++ b/app/views/devise/shared/_error_messages.html.erb @@ -0,0 +1,15 @@ +<% if resource.errors.any? %> +
+

+ <%= I18n.t("errors.messages.not_saved", + count: resource.errors.count, + resource: resource.class.model_name.human.downcase) + %> +

+ +
+<% end %> diff --git a/app/views/devise/shared/_links.html.erb b/app/views/devise/shared/_links.html.erb new file mode 100644 index 00000000..7a75304b --- /dev/null +++ b/app/views/devise/shared/_links.html.erb @@ -0,0 +1,25 @@ +<%- if controller_name != 'sessions' %> + <%= link_to "Log in", new_session_path(resource_name) %>
+<% end %> + +<%- if devise_mapping.registerable? && controller_name != 'registrations' %> + <%= link_to "Sign up", new_registration_path(resource_name) %>
+<% end %> + +<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %> + <%= link_to "Forgot your password?", new_password_path(resource_name) %>
+<% end %> + +<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %> + <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %>
+<% end %> + +<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %> + <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>
+<% end %> + +<%- if devise_mapping.omniauthable? %> + <%- resource_class.omniauth_providers.each do |provider| %> + <%= button_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider), data: { turbo: false } %>
+ <% end %> +<% end %> diff --git a/app/views/devise/unlocks/new.html.erb b/app/views/devise/unlocks/new.html.erb new file mode 100644 index 00000000..ffc34de8 --- /dev/null +++ b/app/views/devise/unlocks/new.html.erb @@ -0,0 +1,16 @@ +

Resend unlock instructions

+ +<%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %> + <%= render "devise/shared/error_messages", resource: resource %> + +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email" %> +
+ +
+ <%= f.submit "Resend unlock instructions" %> +
+<% end %> + +<%= render "devise/shared/links" %> diff --git a/app/views/news_details/_news_item.html.erb b/app/views/news_details/_news_item.html.erb new file mode 100644 index 00000000..733d112f --- /dev/null +++ b/app/views/news_details/_news_item.html.erb @@ -0,0 +1,19 @@ +
  • + + <%= news_detail.title %> + <%= link_to 'Article', news_detail.url %> +

    Liked by: + <%= news_detail.users.pluck(:first_name).join(', ') %> +

    + + +<%= button_to 'Upvote', upvote_news_detail_path(news_detail, status: 'updated'), method: :patch, remote: true, class: 'update-button' %> +<%= button_to 'Downvote', downvote_news_detail_path(news_detail, status: 'updated'), method: :patch, remote: true, class: 'update-button' %> + + + +
  • + + + + \ No newline at end of file diff --git a/app/views/news_details/index.html.erb b/app/views/news_details/index.html.erb new file mode 100644 index 00000000..edf8b0d0 --- /dev/null +++ b/app/views/news_details/index.html.erb @@ -0,0 +1,15 @@ +

    Recent Stories

    + +<%= paginate @news_details %> + +<%= link_to "Log out", destroy_user_session_path, method: :delete, class: "btn btn-danger" %> +<%= link_to "Liked Stories", liked_index_news_details_path %> + + + +<%= paginate @news_details %> + diff --git a/app/views/news_details/liked_index.html.erb b/app/views/news_details/liked_index.html.erb new file mode 100644 index 00000000..35a717ef --- /dev/null +++ b/app/views/news_details/liked_index.html.erb @@ -0,0 +1,11 @@ +

    Liked Stories

    +<%= link_to "Log out", destroy_user_session_path, method: :delete, class: "btn btn-danger" %> + +<%= link_to "Recent Top Stories", news_details_path %> + + + diff --git a/bin/rake b/bin/rake index d87d5f57..0ba8c48c 100755 --- a/bin/rake +++ b/bin/rake @@ -1,6 +1,6 @@ #!/usr/bin/env ruby begin - load File.expand_path('../spring', __FILE__) + load File.expand_path('spring', __dir__) rescue LoadError => e raise unless e.message.include?('spring') end diff --git a/bin/setup b/bin/setup index 78c4e861..b2293a35 100755 --- a/bin/setup +++ b/bin/setup @@ -4,7 +4,7 @@ require 'fileutils' include FileUtils # path to your application root. -APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) +APP_ROOT = Pathname.new File.expand_path('..', __dir__) def system!(*args) system(*args) || abort("\n== Command #{args} failed ==") @@ -21,7 +21,6 @@ chdir APP_ROOT do # Install JavaScript dependencies if using Yarn # system('bin/yarn') - # puts "\n== Copying sample files ==" # unless File.exist?('config/database.yml') # cp 'config/database.yml.sample', 'config/database.yml' diff --git a/bin/spring b/bin/spring index fb2ec2eb..991bd4ef 100755 --- a/bin/spring +++ b/bin/spring @@ -8,7 +8,7 @@ unless defined?(Spring) require 'bundler' lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read) - spring = lockfile.specs.detect { |spec| spec.name == "spring" } + spring = lockfile.specs.detect { |spec| spec.name == 'spring' } if spring Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path gem 'spring', spring.version diff --git a/bin/update b/bin/update index a8e4462f..32326c74 100755 --- a/bin/update +++ b/bin/update @@ -4,7 +4,7 @@ require 'fileutils' include FileUtils # path to your application root. -APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) +APP_ROOT = Pathname.new File.expand_path('..', __dir__) def system!(*args) system(*args) || abort("\n== Command #{args} failed ==") diff --git a/bin/yarn b/bin/yarn index c2bacef8..fc0bd659 100755 --- a/bin/yarn +++ b/bin/yarn @@ -1,11 +1,9 @@ #!/usr/bin/env ruby VENDOR_PATH = File.expand_path('..', __dir__) Dir.chdir(VENDOR_PATH) do - begin - exec "yarnpkg #{ARGV.join(" ")}" - rescue Errno::ENOENT - $stderr.puts "Yarn executable was not detected in the system." - $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" - exit 1 - end + exec "yarnpkg #{ARGV.join(' ')}" +rescue Errno::ENOENT + warn 'Yarn executable was not detected in the system.' + warn 'Download Yarn at https://yarnpkg.com/en/docs/install' + exit 1 end diff --git a/config/application.rb b/config/application.rb index dab4cec6..9c44baff 100644 --- a/config/application.rb +++ b/config/application.rb @@ -14,5 +14,6 @@ class Application < Rails::Application # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers # -- all .rb files in that directory are automatically loaded. + config.active_record.legacy_connection_handling = false end end diff --git a/config/environments/production.rb b/config/environments/production.rb index 73aa41e6..97009666 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -52,7 +52,7 @@ config.log_level = :debug # Prepend all log lines with the following tags. - config.log_tags = [ :request_id ] + config.log_tags = [:request_id] # Use a different cache store in production. # config.cache_store = :mem_cache_store @@ -80,7 +80,7 @@ # require 'syslog/logger' # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') - if ENV["RAILS_LOG_TO_STDOUT"].present? + if ENV['RAILS_LOG_TO_STDOUT'].present? logger = ActiveSupport::Logger.new(STDOUT) logger.formatter = config.log_formatter config.logger = ActiveSupport::TaggedLogging.new(logger) diff --git a/config/environments/test.rb b/config/environments/test.rb index 8e5cbde5..7b6dc4c0 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -5,7 +5,7 @@ # test suite. You never need to work with it otherwise. Remember that # your test database is "scratch space" for the test suite and is wiped # and recreated between test runs. Don't rely on the data there! - config.cache_classes = true + config.cache_classes = false # Do not eager load code on boot. This avoids loading your whole application # just for the purpose of running a single test. If you are using a tool that diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 962d4a7c..4cd56307 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -1,5 +1,11 @@ # frozen_string_literal: true +# Assuming you have not yet modified this file, each configuration option below +# is set to its default value. Note that some are commented out while others +# are not: uncommented lines are intended to protect your configuration from +# breaking changes in upgrades (i.e., in the event that future versions of +# Devise change the default values for those options). +# # Use this hook to configure devise mailer, warden hooks and so forth. # Many of these configuration options can be set straight in your model. Devise.setup do |config| @@ -8,7 +14,11 @@ # confirmation, reset password and unlock tokens in the database. # Devise will use the `secret_key_base` as its `secret_key` # by default. You can change it below and use your own secret key. - # config.secret_key = '3d8fdc791713e92d72d135adbaa58657b3661394b046a293632d59b4cf4ba76fc5369d8f67202bce93a62fc0aacf24878bcd5f7879d859b588919783efcf7eb3' + # config.secret_key = 'ff6ef85093968d28e61e663f250fee4c2be20c55e38d30f168b726002d04681a0fe9545755e26484a4d8e4feae1bf613d9b5d770ee7b8568305930302cc3fc3e' + + # ==> Controller configuration + # Configure the parent class to the devise controllers. + # config.parent_controller = 'DeviseController' # ==> Mailer Configuration # Configure the e-mail address which will be shown in Devise::Mailer, @@ -64,7 +74,10 @@ # Tell if authentication through HTTP Auth is enabled. False by default. # It can be set to an array that will enable http authentication only for the # given strategies, for example, `config.http_authenticatable = [:database]` will - # enable it only for database authentication. The supported strategies are: + # enable it only for database authentication. + # For API-only applications to support authentication "out-of-the-box", you will likely want to + # enable this with :database unless you are using a custom strategy. + # The supported strategies are: # :database = Support basic authentication with authentication key + password # config.http_authenticatable = false @@ -99,18 +112,21 @@ # config.reload_routes = true # ==> Configuration for :database_authenticatable - # For bcrypt, this is the cost for hashing the password and defaults to 11. If + # For bcrypt, this is the cost for hashing the password and defaults to 12. If # using other algorithms, it sets how many times you want the password to be hashed. + # The number of stretches used for generating the hashed password are stored + # with the hashed password. This allows you to change the stretches without + # invalidating existing passwords. # # Limiting the stretches to just one in testing will increase the performance of # your test suite dramatically. However, it is STRONGLY RECOMMENDED to not use # a value less than 10 in other environments. Note that, for bcrypt (the default # algorithm), the cost increases exponentially with the number of stretches (e.g. # a value of 20 is already extremely slow: approx. 60 seconds for 1 calculation). - config.stretches = Rails.env.test? ? 1 : 11 + config.stretches = Rails.env.test? ? 1 : 12 # Set up a pepper to generate the hashed password. - # config.pepper = '98f368775fc8e418915aa4d2185d5659bda9b2a2aae35a6bb5c2863536c1c7e1ff8884578d43324639ae4d9bc4c851a7c71739103378841fd7f8f9e0db32daea' + # config.pepper = '87001eb32fbe9f99917b7de4e80686792725077b33fd5c5bffd960ff88ad7082df369cc6626fd9e19c86d4be23a9173a74c7731f63158c317addfb3f7ae0f621' # Send a notification to the original email when the user's email is changed. # config.send_email_changed_notification = false @@ -122,8 +138,11 @@ # A period that the user is allowed to access the website even without # confirming their account. For instance, if set to 2.days, the user will be # able to access the website for two days without confirming their account, - # access will be blocked just in the third day. Default is 0.days, meaning - # the user cannot access the website without confirming their account. + # access will be blocked just in the third day. + # You can also set it to nil, which will allow the user to access the website + # without confirming their account. + # Default is 0.days, meaning the user cannot access the website without + # confirming their account. # config.allow_unconfirmed_access_for = 2.days # A period that the user is allowed to confirm their account before their @@ -237,14 +256,14 @@ # ==> Navigation configuration # Lists the formats that should be treated as navigational. Formats like - # :html, should redirect to the sign in page when the user does not have + # :html should redirect to the sign in page when the user does not have # access, but formats like :xml or :json, should return 401. # # If you have any extra navigational formats, like :iphone or :mobile, you # should add them to the navigational formats lists. # # The "*/*" below is required to match Internet Explorer requests. - # config.navigational_formats = ['*/*', :html] + # config.navigational_formats = ['*/*', :html, :turbo_stream] # The default HTTP method used to sign out a resource. Default is :delete. config.sign_out_via = :delete @@ -276,4 +295,19 @@ # When using OmniAuth, Devise cannot automatically set OmniAuth path, # so you need to do it manually. For the users scope, it would be: # config.omniauth_path_prefix = '/my_engine/users/auth' + + # ==> Hotwire/Turbo configuration + # When using Devise with Hotwire/Turbo, the http status for error responses + # and some redirects must match the following. The default in Devise for existing + # apps is `200 OK` and `302 Found` respectively, but new apps are generated with + # these new defaults that match Hotwire/Turbo behavior. + # Note: These might become the new default in future versions of Devise. + config.responder.error_status = :unprocessable_entity + config.responder.redirect_status = :see_other + + # ==> Configuration for :registerable + + # When set to false, does not sign a user in automatically after their password is + # changed. Defaults to true, so a user is signed in automatically after changing a password. + # config.sign_in_after_change_password = true end diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb new file mode 100644 index 00000000..7d0429e6 --- /dev/null +++ b/config/initializers/sidekiq.rb @@ -0,0 +1,9 @@ +require 'sidekiq' +require 'sidekiq-scheduler' + +Sidekiq.configure_server do |config| + config.on(:startup) do + Sidekiq.schedule = YAML.load_file(File.expand_path('../sidekiq_scheduler.yml', __dir__)) + SidekiqScheduler::Scheduler.instance.reload_schedule! + end +end diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml index 0b8f1302..260e1c4b 100644 --- a/config/locales/devise.en.yml +++ b/config/locales/devise.en.yml @@ -1,4 +1,4 @@ -# Additional translations at https://github.com/plataformatec/devise/wiki/I18n +# Additional translations at https://github.com/heartcombo/devise/wiki/I18n en: devise: @@ -42,8 +42,9 @@ en: signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated." signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked." signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account." - update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirm link to confirm your new email address." + update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirmation link to confirm your new email address." updated: "Your account has been updated successfully." + updated_but_not_signed_in: "Your account has been updated successfully, but since your password was changed, you need to sign in again." sessions: signed_in: "Signed in successfully." signed_out: "Signed out successfully." diff --git a/config/puma.rb b/config/puma.rb index 1e19380d..ccda173c 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -4,16 +4,16 @@ # the maximum value specified for Puma. Default is set to 5 threads for minimum # and maximum; this matches the default thread size of Active Record. # -threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } +threads_count = ENV.fetch('RAILS_MAX_THREADS') { 5 } threads threads_count, threads_count # Specifies the `port` that Puma will listen on to receive requests; default is 3000. # -port ENV.fetch("PORT") { 3000 } +port ENV.fetch('PORT') { 3000 } # Specifies the `environment` that Puma will run in. # -environment ENV.fetch("RAILS_ENV") { "development" } +environment ENV.fetch('RAILS_ENV') { 'development' } # Specifies the number of `workers` to boot in clustered mode. # Workers are forked webserver processes. If using threads and workers together diff --git a/config/routes.rb b/config/routes.rb index c12ef082..48cccb16 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,14 @@ Rails.application.routes.draw do - devise_for :users - root to: 'pages#home' + devise_for :users, only: [:sessions] + root to: 'news_details#index' + + resources :news_details, only: [:index] do + collection do + get :liked_index + end + member do + patch :upvote + patch :downvote + end + end end diff --git a/config/sidekiq_scheduler.yml b/config/sidekiq_scheduler.yml new file mode 100644 index 00000000..a8da8fb4 --- /dev/null +++ b/config/sidekiq_scheduler.yml @@ -0,0 +1,4 @@ +poll_hacker_news_job: + every: ['5m', first_in: '0s'] + class: PollHackerNewsJob + queue: default \ No newline at end of file diff --git a/config/spring.rb b/config/spring.rb index c9119b40..9fa7863f 100644 --- a/config/spring.rb +++ b/config/spring.rb @@ -1,6 +1,6 @@ -%w( +%w[ .ruby-version .rbenv-vars tmp/restart.txt tmp/caching-dev.txt -).each { |path| Spring.watch(path) } +].each { |path| Spring.watch(path) } diff --git a/db/migrate/20180228212101_devise_create_users.rb b/db/migrate/20180228212101_devise_create_users.rb index c6808c47..6e7fe031 100644 --- a/db/migrate/20180228212101_devise_create_users.rb +++ b/db/migrate/20180228212101_devise_create_users.rb @@ -7,8 +7,8 @@ def change t.string :last_name ## Database authenticatable - t.string :email, null: false, default: "" - t.string :encrypted_password, null: false, default: "" + t.string :email, null: false, default: '' + t.string :encrypted_password, null: false, default: '' ## Recoverable t.string :reset_password_token @@ -35,7 +35,6 @@ def change # t.string :unlock_token # Only if unlock strategy is :email or :both # t.datetime :locked_at - t.timestamps null: false end diff --git a/db/migrate/20240812194904_create_news_details.rb b/db/migrate/20240812194904_create_news_details.rb new file mode 100644 index 00000000..f313cecb --- /dev/null +++ b/db/migrate/20240812194904_create_news_details.rb @@ -0,0 +1,14 @@ +class CreateNewsDetails < ActiveRecord::Migration[7.0] + def change + create_table :news_details do |t| + t.string :author # By in HN + t.integer :comment_count # Decendents in HN + t.integer :hn_id # HN id + t.string :url # HN url + t.integer :score # HN score + t.string :title # HN story title + t.string :story_type # HN story type + t.timestamps + end + end +end diff --git a/db/migrate/20240813041304_create_user_liked_stories.rb b/db/migrate/20240813041304_create_user_liked_stories.rb new file mode 100644 index 00000000..8ec8652d --- /dev/null +++ b/db/migrate/20240813041304_create_user_liked_stories.rb @@ -0,0 +1,9 @@ +class CreateUserLikedStories < ActiveRecord::Migration[7.0] + def change + create_join_table :users, :news_details do |t| + t.index :user_id + t.index :news_detail_id + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index acc34f3b..89b70f94 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,27 +10,47 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2018_02_28_212101) do +ActiveRecord::Schema[7.0].define(version: 20_240_813_041_304) do # These are extensions that must be enabled in order to support this database - enable_extension "plpgsql" + enable_extension 'plpgsql' - create_table "users", force: :cascade do |t| - t.string "first_name" - t.string "last_name" - t.string "email", default: "", null: false - t.string "encrypted_password", default: "", null: false - t.string "reset_password_token" - t.datetime "reset_password_sent_at", precision: nil - t.datetime "remember_created_at", precision: nil - t.integer "sign_in_count", default: 0, null: false - t.datetime "current_sign_in_at", precision: nil - t.datetime "last_sign_in_at", precision: nil - t.inet "current_sign_in_ip" - t.inet "last_sign_in_ip" - t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false - t.index ["email"], name: "index_users_on_email", unique: true - t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true + create_table 'news_details', force: :cascade do |t| + t.string 'author' + t.integer 'comment_count' + t.integer 'hn_id' + t.string 'url' + t.integer 'score' + t.string 'title' + t.string 'story_type' + t.datetime 'created_at', null: false + t.datetime 'updated_at', null: false end + create_table 'news_details_users', id: false, force: :cascade do |t| + t.bigint 'user_id', null: false + t.bigint 'news_detail_id', null: false + t.datetime 'created_at', null: false + t.datetime 'updated_at', null: false + t.index ['news_detail_id'], name: 'index_news_details_users_on_news_detail_id' + t.index ['user_id'], name: 'index_news_details_users_on_user_id' + end + + create_table 'users', force: :cascade do |t| + t.string 'first_name' + t.string 'last_name' + t.string 'email', default: '', null: false + t.string 'encrypted_password', default: '', null: false + t.string 'reset_password_token' + t.datetime 'reset_password_sent_at', precision: nil + t.datetime 'remember_created_at', precision: nil + t.integer 'sign_in_count', default: 0, null: false + t.datetime 'current_sign_in_at', precision: nil + t.datetime 'last_sign_in_at', precision: nil + t.inet 'current_sign_in_ip' + t.inet 'last_sign_in_ip' + t.datetime 'created_at', precision: nil, null: false + t.datetime 'updated_at', precision: nil, null: false + t.index ['email'], name: 'index_users_on_email', unique: true + t.index ['reset_password_token'], name: 'index_users_on_reset_password_token', unique: true + end end diff --git a/db/seeds.rb b/db/seeds.rb index 231b90ad..b0afe6c3 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -1,17 +1,17 @@ User.find_or_initialize_by(email: 'DonaldGMiller@example.com').update({ - first_name: 'Donald', - last_name: 'Miller', - password: 'eeMaev2shai' -}) + first_name: 'Donald', + last_name: 'Miller', + password: 'eeMaev2shai' + }) User.find_or_initialize_by(email: 'LawrenceWGrant@example.com').update({ - first_name: 'Lawrence', - last_name: 'Grant', - password: 'ahR7iecai' -}) + first_name: 'Lawrence', + last_name: 'Grant', + password: 'ahR7iecai' + }) User.find_or_initialize_by(email: 'MargeRWilliams@example.com').update({ - first_name: 'Marge', - last_name: 'Williams', - password: 'Aechugh1ie' -}) + first_name: 'Marge', + last_name: 'Williams', + password: 'Aechugh1ie' + }) diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index b51dc1c3..fed44d41 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -1,16 +1,16 @@ require 'rails_helper' describe User do - context "creating a new user" do + context 'creating a new user' do let(:attrs) do { first_name: :foo, last_name: :bar, email: 'f@b.c', password: 'foobar123' } end - it "should have first, last, email" do - expect { User.create(attrs) }.to change{ User.count }.by(1) + it 'should have first, last, email' do + expect { User.create(attrs) }.to change { User.count }.by(1) end - it "should require a password" do + it 'should require a password' do expect(User.new(attrs.except(:password))).to be_invalid end end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index bbe1ba57..2cedbfff 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -1,9 +1,9 @@ # This file is copied to spec/ when you run 'rails generate rspec:install' require 'spec_helper' ENV['RAILS_ENV'] ||= 'test' -require File.expand_path('../../config/environment', __FILE__) +require File.expand_path('../config/environment', __dir__) # Prevent database truncation if the environment is production -abort("The Rails environment is running in production mode!") if Rails.env.production? +abort('The Rails environment is running in production mode!') if Rails.env.production? require 'rspec/rails' # Add additional requires below this line. Rails is not loaded until this point! diff --git a/spec/services/hacker_news/add_new_stories_spec.rb b/spec/services/hacker_news/add_new_stories_spec.rb new file mode 100644 index 00000000..95df3ca4 --- /dev/null +++ b/spec/services/hacker_news/add_new_stories_spec.rb @@ -0,0 +1,30 @@ +require 'rails_helper' + +RSpec.describe HackerNews::AddNewStories do + subject(:service) { described_class } + + describe '#call' do + context 'when service is called' do + it 'adds new stories' do + result = service.new([41_234_588]).call + expect(result.success?).to be true + end + + it 'fails to add' do + result = service.new([99_999_999_111]).call + expect(result.success?).to be false + end + end + + context 'does not add if story is already save in NewsDetail' do + it 'does not save' do + service.new([41_229_573, 41_229_555, 41_235_733, 41_235_723, 41_235_721]).call + + service.new([41_229_600, 41_229_597, 41_229_595, 41_229_589, 41_229_582, + 41_229_573, 41_229_555, 41_235_733, 41_235_723, 41_235_721]).call + + expect(NewsDetail.count).to eq(10) + end + end + end +end diff --git a/spec/services/hacker_news/base_spec.rb b/spec/services/hacker_news/base_spec.rb new file mode 100644 index 00000000..e69de29b diff --git a/spec/services/hacker_news/create_item_details_spec.rb b/spec/services/hacker_news/create_item_details_spec.rb new file mode 100644 index 00000000..cf9ca8cb --- /dev/null +++ b/spec/services/hacker_news/create_item_details_spec.rb @@ -0,0 +1,26 @@ +require 'rails_helper' + +RSpec.describe HackerNews::CreateItemDetail do + subject(:service) { described_class } + + describe '#call' do + context 'creates new NewsDetail from HN story ID' do + it 'saves new news detail' do + result = service.new(41_234_585).call + + expect(result.success?).to be true + end + + it 'does not save duplicates' do + service.new(41_234_585).call + result = service.new(41_234_585).call + expect(result.success?).to be false + end + + it 'does not save invalid data' do + result = service.new(4_123_458_599_999).call + expect(result.success?).to be false + end + end + end +end diff --git a/spec/services/hacker_news/get_story_ids_spec.rb b/spec/services/hacker_news/get_story_ids_spec.rb new file mode 100644 index 00000000..0864109e --- /dev/null +++ b/spec/services/hacker_news/get_story_ids_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +RSpec.describe HackerNews::GetStoryIds do + subject(:service) { described_class.new } + + describe '#call' do + context 'when service is called' do + it 'gets storu ids' do + result = service.call + + expect(result.success?).to be true + end + end + end +end diff --git a/spec/sidekiq/poll_hacker_news_job_spec.rb b/spec/sidekiq/poll_hacker_news_job_spec.rb new file mode 100644 index 00000000..49a4e8e0 --- /dev/null +++ b/spec/sidekiq/poll_hacker_news_job_spec.rb @@ -0,0 +1,18 @@ +require 'rails_helper' +RSpec.describe PollHackerNewsJob do + subject(:service) { described_class } + + let(:news_detail) { NewsDetail.create(hn_id: 1) } + describe '#perform' do + it 'intial perfrom populates db' do + service.new.perform + expect(NewsDetail.count > 0).to be true + end + + it 'addtional performs will add to db' do + news_detail + service.new.perform + expect(NewsDetail.count > 1).to be true + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index ce33d66d..15a38725 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -44,53 +44,51 @@ # triggering implicit auto-inclusion in groups with matching metadata. config.shared_context_metadata_behavior = :apply_to_host_groups -# The settings below are suggested to provide a good initial experience -# with RSpec, but feel free to customize to your heart's content. -=begin - # This allows you to limit a spec run to individual examples or groups - # you care about by tagging them with `:focus` metadata. When nothing - # is tagged with `:focus`, all examples get run. RSpec also provides - # aliases for `it`, `describe`, and `context` that include `:focus` - # metadata: `fit`, `fdescribe` and `fcontext`, respectively. - config.filter_run_when_matching :focus - - # Allows RSpec to persist some state between runs in order to support - # the `--only-failures` and `--next-failure` CLI options. We recommend - # you configure your source control system to ignore this file. - config.example_status_persistence_file_path = "spec/examples.txt" - - # Limits the available syntax to the non-monkey patched syntax that is - # recommended. For more details, see: - # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ - # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ - # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode - config.disable_monkey_patching! - - # Many RSpec users commonly either run the entire suite or an individual - # file, and it's useful to allow more verbose output when running an - # individual spec file. - if config.files_to_run.one? - # Use the documentation formatter for detailed output, - # unless a formatter has already been configured - # (e.g. via a command-line flag). - config.default_formatter = "doc" - end - - # Print the 10 slowest examples and example groups at the - # end of the spec run, to help surface which specs are running - # particularly slow. - config.profile_examples = 10 - - # Run specs in random order to surface order dependencies. If you find an - # order dependency and want to debug it, you can fix the order by providing - # the seed, which is printed after each run. - # --seed 1234 - config.order = :random - - # Seed global randomization in this process using the `--seed` CLI option. - # Setting this allows you to use `--seed` to deterministically reproduce - # test failures related to randomization by passing the same `--seed` value - # as the one that triggered the failure. - Kernel.srand config.seed -=end + # The settings below are suggested to provide a good initial experience + # with RSpec, but feel free to customize to your heart's content. + # # This allows you to limit a spec run to individual examples or groups + # # you care about by tagging them with `:focus` metadata. When nothing + # # is tagged with `:focus`, all examples get run. RSpec also provides + # # aliases for `it`, `describe`, and `context` that include `:focus` + # # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + # config.filter_run_when_matching :focus + # + # # Allows RSpec to persist some state between runs in order to support + # # the `--only-failures` and `--next-failure` CLI options. We recommend + # # you configure your source control system to ignore this file. + # config.example_status_persistence_file_path = "spec/examples.txt" + # + # # Limits the available syntax to the non-monkey patched syntax that is + # # recommended. For more details, see: + # # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + # config.disable_monkey_patching! + # + # # Many RSpec users commonly either run the entire suite or an individual + # # file, and it's useful to allow more verbose output when running an + # # individual spec file. + # if config.files_to_run.one? + # # Use the documentation formatter for detailed output, + # # unless a formatter has already been configured + # # (e.g. via a command-line flag). + # config.default_formatter = "doc" + # end + # + # # Print the 10 slowest examples and example groups at the + # # end of the spec run, to help surface which specs are running + # # particularly slow. + # config.profile_examples = 10 + # + # # Run specs in random order to surface order dependencies. If you find an + # # order dependency and want to debug it, you can fix the order by providing + # # the seed, which is printed after each run. + # # --seed 1234 + # config.order = :random + # + # # Seed global randomization in this process using the `--seed` CLI option. + # # Setting this allows you to use `--seed` to deterministically reproduce + # # test failures related to randomization by passing the same `--seed` value + # # as the one that triggered the failure. + # Kernel.srand config.seed end diff --git a/test/application_system_test_case.rb b/test/application_system_test_case.rb index d19212ab..23701b43 100644 --- a/test/application_system_test_case.rb +++ b/test/application_system_test_case.rb @@ -1,4 +1,4 @@ -require "test_helper" +require 'test_helper' class ApplicationSystemTestCase < ActionDispatch::SystemTestCase driven_by :selenium, using: :chrome, screen_size: [1400, 1400] diff --git a/test/test_helper.rb b/test/test_helper.rb index 92e39b2d..88d8f433 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,5 +1,5 @@ ENV['RAILS_ENV'] ||= 'test' -require File.expand_path('../../config/environment', __FILE__) +require File.expand_path('../config/environment', __dir__) require 'rails/test_help' class ActiveSupport::TestCase