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)
+ %>
+
+
+ <% resource.errors.full_messages.each do |message| %>
+ - <%= message %>
+ <% end %>
+
+
+<% 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 %>
+
+
+ <% @news_details.each do |news_detail| %>
+ <%= render partial: "news_item", locals: {news_detail: news_detail} %>
+ <% end %>
+
+
+<%= 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 %>
+
+
+ <% @news_details.each do |news_detail| %>
+ <%= render partial: "news_item", locals: {news_detail: news_detail} %>
+ <% end %>
+
+
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