From e04a9c7736b5386454f8121cef072dd8a74f87a8 Mon Sep 17 00:00:00 2001 From: "Kevin Y. Kim" Date: Thu, 22 Sep 2022 15:48:57 -0400 Subject: [PATCH 1/7] update to Ruby 3.1.2 / Rails 7.0.4 --- .ruby-version | 2 +- Gemfile | 6 +- Gemfile.lock | 271 +++++++++++++++++++++++++++----------------------- db/schema.rb | 15 ++- db/seeds.rb | 6 +- 5 files changed, 163 insertions(+), 137 deletions(-) diff --git a/.ruby-version b/.ruby-version index bff6ce5c..ef538c28 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -ruby-2.7.1 +3.1.2 diff --git a/Gemfile b/Gemfile index 0eb15ca0..21324fc0 100644 --- a/Gemfile +++ b/Gemfile @@ -1,5 +1,6 @@ source 'https://rubygems.org' -ruby '2.7.1' + +ruby File.read('.ruby-version').chomp gem 'byebug', platforms: [:mri, :mingw, :x64_mingw], group: [:development, :test] gem 'capybara', group: [:development, :test] @@ -11,12 +12,11 @@ gem 'listen', group: :development gem 'pg' gem 'pry-rails' gem 'puma' -gem 'rails' +gem 'rails', '~> 7.0.3' gem 'rspec-rails' gem 'sass-rails' gem 'selenium-webdriver', group: [:development, :test] gem 'spring', group: :development -gem 'spring-watcher-listen', group: :development gem 'turbolinks' gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] gem 'uglifier' diff --git a/Gemfile.lock b/Gemfile.lock index 677b6269..b4b09030 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,80 +1,87 @@ GEM remote: https://rubygems.org/ specs: - actioncable (6.1.4) - actionpack (= 6.1.4) - activesupport (= 6.1.4) + actioncable (7.0.4) + actionpack (= 7.0.4) + activesupport (= 7.0.4) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.1.4) - actionpack (= 6.1.4) - activejob (= 6.1.4) - activerecord (= 6.1.4) - activestorage (= 6.1.4) - activesupport (= 6.1.4) + 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) - actionmailer (6.1.4) - actionpack (= 6.1.4) - actionview (= 6.1.4) - activejob (= 6.1.4) - activesupport (= 6.1.4) + 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 (6.1.4) - actionview (= 6.1.4) - activesupport (= 6.1.4) - rack (~> 2.0, >= 2.0.9) + 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 (6.1.4) - actionpack (= 6.1.4) - activerecord (= 6.1.4) - activestorage (= 6.1.4) - activesupport (= 6.1.4) + 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 (6.1.4) - activesupport (= 6.1.4) + 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 (6.1.4) - activesupport (= 6.1.4) + activejob (7.0.4) + activesupport (= 7.0.4) globalid (>= 0.3.6) - activemodel (6.1.4) - activesupport (= 6.1.4) - activerecord (6.1.4) - activemodel (= 6.1.4) - activesupport (= 6.1.4) - activestorage (6.1.4) - actionpack (= 6.1.4) - activejob (= 6.1.4) - activerecord (= 6.1.4) - activesupport (= 6.1.4) - marcel (~> 1.0.0) + 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 (6.1.4) + activesupport (7.0.4) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) - zeitwerk (~> 2.3) - addressable (2.8.0) - public_suffix (>= 2.0.2, < 5.0) - bcrypt (3.1.16) + 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.35.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 (3.0.0) + childprocess (4.1.0) coderay (1.1.3) coffee-rails (5.0.0) coffee-script (>= 2.2.0) @@ -83,101 +90,120 @@ GEM coffee-script-source execjs coffee-script-source (1.12.2) - concurrent-ruby (1.1.9) + concurrent-ruby (1.1.10) crass (1.0.6) - devise (4.8.0) + devise (4.8.1) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 4.1.0) responders warden (~> 1.2.3) - diff-lcs (1.4.4) - erubi (1.10.0) + diff-lcs (1.5.0) + digest (3.1.0) + erubi (1.11.0) execjs (2.8.1) - ffi (1.15.3) - globalid (0.4.2) - activesupport (>= 4.2.0) - haml (5.2.1) - temple (>= 0.8.0) + ffi (1.15.5) + globalid (1.0.0) + activesupport (>= 5.0) + haml (6.0.0) + temple (>= 0.8.2) + thor tilt - i18n (1.8.10) + i18n (1.12.0) concurrent-ruby (~> 1.0) - jbuilder (2.11.2) + jbuilder (2.11.5) + actionview (>= 5.0.0) activesupport (>= 5.0.0) - listen (3.5.1) + listen (3.7.1) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) - loofah (2.10.0) + loofah (2.19.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) mail (2.7.1) mini_mime (>= 0.1.1) - marcel (1.0.1) + marcel (1.0.2) + matrix (0.4.2) method_source (1.0.0) - mini_mime (1.1.0) - mini_portile2 (2.5.3) - minitest (5.14.4) + 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.11.7) - mini_portile2 (~> 2.5.0) + nokogiri (1.13.8) + mini_portile2 (~> 2.8.0) racc (~> 1.4) orm_adapter (0.5.0) - pg (1.2.3) + 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 (4.0.6) - puma (5.6.2) + public_suffix (5.0.0) + puma (5.6.5) nio4r (~> 2.0) - racc (1.5.2) - rack (2.2.3) - rack-test (1.1.0) - rack (>= 1.0, < 3) - rails (6.1.4) - actioncable (= 6.1.4) - actionmailbox (= 6.1.4) - actionmailer (= 6.1.4) - actionpack (= 6.1.4) - actiontext (= 6.1.4) - actionview (= 6.1.4) - activejob (= 6.1.4) - activemodel (= 6.1.4) - activerecord (= 6.1.4) - activestorage (= 6.1.4) - activesupport (= 6.1.4) + 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 (= 6.1.4) - sprockets-rails (>= 2.0.0) + railties (= 7.0.4) rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) - rails-html-sanitizer (1.3.0) + rails-html-sanitizer (1.4.3) loofah (~> 2.3) - railties (6.1.4) - actionpack (= 6.1.4) - activesupport (= 6.1.4) + railties (7.0.4) + actionpack (= 7.0.4) + activesupport (= 7.0.4) method_source - rake (>= 0.13) + rake (>= 12.2) thor (~> 1.0) - rake (13.0.3) - rb-fsevent (0.11.0) + zeitwerk (~> 2.5) + rake (13.0.6) + rb-fsevent (0.11.2) rb-inotify (0.10.1) ffi (~> 1.0) - regexp_parser (2.1.1) + regexp_parser (2.5.0) responders (3.0.1) actionpack (>= 5.0) railties (>= 5.0) - rspec-core (3.10.1) - rspec-support (~> 3.10.0) - rspec-expectations (3.10.1) + 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.10.0) - rspec-mocks (3.10.2) + rspec-support (~> 3.11.0) + rspec-mocks (3.11.1) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.10.0) - rspec-rails (5.0.1) + rspec-support (~> 3.11.0) + rspec-rails (5.1.2) actionpack (>= 5.2) activesupport (>= 5.2) railties (>= 5.2) @@ -185,8 +211,8 @@ GEM rspec-expectations (~> 3.10) rspec-mocks (~> 3.10) rspec-support (~> 3.10) - rspec-support (3.10.2) - rubyzip (2.3.0) + 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) @@ -197,43 +223,45 @@ GEM sprockets (> 3.0) sprockets-rails tilt - selenium-webdriver (3.142.7) - childprocess (>= 0.5, < 4.0) - rubyzip (>= 1.2.2) - spring (2.1.1) - spring-watcher-listen (2.0.1) - listen (>= 2.7, < 4.0) - spring (>= 1.2, < 3.0) - sprockets (4.0.2) + 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.2.2) - actionpack (>= 4.0) - activesupport (>= 4.0) + sprockets-rails (3.4.2) + actionpack (>= 5.2) + activesupport (>= 5.2) sprockets (>= 3.0.0) + strscan (3.0.4) temple (0.8.2) - thor (1.1.0) - tilt (2.0.10) + 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.4) + 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.1.0) + 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.4.2) + zeitwerk (2.6.0) PLATFORMS ruby @@ -249,19 +277,18 @@ DEPENDENCIES pg pry-rails puma - rails + rails (~> 7.0.3) rspec-rails sass-rails selenium-webdriver spring - spring-watcher-listen turbolinks tzinfo-data uglifier web-console RUBY VERSION - ruby 2.7.1p83 + ruby 3.1.2p20 BUNDLED WITH - 2.2.16 + 2.3.22 diff --git a/db/schema.rb b/db/schema.rb index df5f0df7..acc34f3b 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,8 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2018_02_28_212101) do - +ActiveRecord::Schema[7.0].define(version: 2018_02_28_212101) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -21,15 +20,15 @@ t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false t.string "reset_password_token" - t.datetime "reset_password_sent_at" - t.datetime "remember_created_at" + t.datetime "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" - t.datetime "last_sign_in_at" + 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", null: false - t.datetime "updated_at", null: false + 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 diff --git a/db/seeds.rb b/db/seeds.rb index 76b85394..231b90ad 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -1,16 +1,16 @@ -User.find_or_initialize_by(email: 'DonaldGMiller@example.com').update_attributes({ +User.find_or_initialize_by(email: 'DonaldGMiller@example.com').update({ first_name: 'Donald', last_name: 'Miller', password: 'eeMaev2shai' }) -User.find_or_initialize_by(email: 'LawrenceWGrant@example.com').update_attributes({ +User.find_or_initialize_by(email: 'LawrenceWGrant@example.com').update({ first_name: 'Lawrence', last_name: 'Grant', password: 'ahR7iecai' }) -User.find_or_initialize_by(email: 'MargeRWilliams@example.com').update_attributes({ +User.find_or_initialize_by(email: 'MargeRWilliams@example.com').update({ first_name: 'Marge', last_name: 'Williams', password: 'Aechugh1ie' From 9f338ed9139ed39f26580b83c4fb0817b9c11332 Mon Sep 17 00:00:00 2001 From: Mike Enriquez Date: Mon, 12 Dec 2022 13:47:07 -0500 Subject: [PATCH 2/7] Add versions --- .tool-versions | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .tool-versions diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 00000000..6da6866f --- /dev/null +++ b/.tool-versions @@ -0,0 +1,2 @@ +ruby 3.1.2 +postgres 14.4 From 17c9400bb6af1b0bc9d54aa0fed6514d11c02e71 Mon Sep 17 00:00:00 2001 From: Mike Enriquez Date: Mon, 12 Dec 2022 13:47:23 -0500 Subject: [PATCH 3/7] Remove haml --- Gemfile | 1 - Gemfile.lock | 6 ------ app/views/layouts/application.html.erb | 19 +++++++++++++++++++ app/views/layouts/application.html.haml | 12 ------------ app/views/pages/home.html.erb | 1 + app/views/pages/home.html.haml | 1 - 6 files changed, 20 insertions(+), 20 deletions(-) create mode 100644 app/views/layouts/application.html.erb delete mode 100644 app/views/layouts/application.html.haml create mode 100644 app/views/pages/home.html.erb delete mode 100644 app/views/pages/home.html.haml diff --git a/Gemfile b/Gemfile index 21324fc0..5a8ffc43 100644 --- a/Gemfile +++ b/Gemfile @@ -6,7 +6,6 @@ gem 'byebug', platforms: [:mri, :mingw, :x64_mingw], group: [:development, :test gem 'capybara', group: [:development, :test] gem 'coffee-rails' gem 'devise' -gem 'haml' gem 'jbuilder' gem 'listen', group: :development gem 'pg' diff --git a/Gemfile.lock b/Gemfile.lock index b4b09030..14ec6457 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -105,10 +105,6 @@ GEM ffi (1.15.5) globalid (1.0.0) activesupport (>= 5.0) - haml (6.0.0) - temple (>= 0.8.2) - thor - tilt i18n (1.12.0) concurrent-ruby (~> 1.0) jbuilder (2.11.5) @@ -237,7 +233,6 @@ GEM activesupport (>= 5.2) sprockets (>= 3.0.0) strscan (3.0.4) - temple (0.8.2) thor (1.2.1) tilt (2.0.11) timeout (0.3.0) @@ -271,7 +266,6 @@ DEPENDENCIES capybara coffee-rails devise - haml jbuilder listen pg diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb new file mode 100644 index 00000000..331a7ed0 --- /dev/null +++ b/app/views/layouts/application.html.erb @@ -0,0 +1,19 @@ + + + + + Top News + <%= csrf_meta_tags %> + <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> + <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> + + +

+ <%= notice %> +

+

+ <%= alert %> +

+ <%= yield %> + + diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml deleted file mode 100644 index bfdcda24..00000000 --- a/app/views/layouts/application.html.haml +++ /dev/null @@ -1,12 +0,0 @@ -!!! -%html - %head - %meta{content: "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/ - %title Top News - = csrf_meta_tags - = stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' - = javascript_include_tag 'application', 'data-turbolinks-track': 'reload' - %body - %p.notice= notice - %p.alert= alert - = yield diff --git a/app/views/pages/home.html.erb b/app/views/pages/home.html.erb new file mode 100644 index 00000000..8bfd8294 --- /dev/null +++ b/app/views/pages/home.html.erb @@ -0,0 +1 @@ +

Welcome to Top News

diff --git a/app/views/pages/home.html.haml b/app/views/pages/home.html.haml deleted file mode 100644 index dfe79e9b..00000000 --- a/app/views/pages/home.html.haml +++ /dev/null @@ -1 +0,0 @@ -%h1 Welcome to Top News From 8a843ccdbac10d4912abbfdf4f159ddd9c96bcd9 Mon Sep 17 00:00:00 2001 From: Allison Cao Date: Thu, 25 May 2023 13:17:25 -0400 Subject: [PATCH 4/7] task completed --- Gemfile | 34 +- Gemfile.lock | 342 +++++++++++------- app/controllers/application_controller.rb | 16 + app/controllers/articles_controller.rb | 52 +++ app/controllers/services/base.rb | 7 + app/controllers/user_articles_controller.rb | 52 +++ app/controllers/users_controller.rb | 16 + app/models/article.rb | 19 + app/models/user.rb | 9 + app/models/user_article.rb | 6 + app/views/articles/index.html.erb | 55 +++ app/views/articles/liked_articles.html.erb | 35 ++ app/views/articles/show.html.erb | 31 ++ app/views/devise/confirmations/new.html.erb | 16 + .../mailer/confirmation_instructions.html.erb | 5 + .../devise/mailer/email_changed.html.erb | 7 + .../devise/mailer/password_change.html.erb | 3 + .../reset_password_instructions.html.erb | 8 + .../mailer/unlock_instructions.html.erb | 7 + app/views/devise/passwords/edit.html.erb | 25 ++ app/views/devise/passwords/new.html.erb | 16 + app/views/devise/registrations/edit.html.erb | 43 +++ app/views/devise/registrations/new.html.erb | 39 ++ app/views/devise/sessions/new.html.erb | 26 ++ .../devise/shared/_error_messages.html.erb | 15 + app/views/devise/shared/_links.html.erb | 25 ++ app/views/devise/unlocks/new.html.erb | 16 + app/views/liked_articles/index.html.erb | 36 ++ app/views/pages/home.html.erb | 8 + app/views/partials/_add_new_object.html.erb | 4 + app/views/partials/_flash.html.erb | 6 + app/views/partials/_footer.html.erb | 15 + app/views/partials/_nav.html.erb | 9 + app/views/partials/_nav_guest.html.erb | 7 + app/views/partials/_object_options.html.erb | 17 + app/views/partials/_quick_links.html.erb | 11 + app/views/partials/_structure1.html.erb | 15 + app/views/partials/_structure2.html.erb | 18 + app/views/partials/_topbar.html.erb | 11 + app/views/users/_user.html.erb | 6 + app/views/users/index.html.erb | 10 + config/environments/development.rb | 2 +- config/environments/test.rb | 2 +- config/routes.rb | 27 +- db/migrate/20230524183141_create_articles.rb | 13 + .../20230524184051_create_user_articles.rb | 10 + db/schema.rb | 23 +- lib/hackernews/connection.rb | 14 + lib/hackernews/request.rb | 31 ++ spec/models/article_spec.rb | 5 + spec/models/user_article_spec.rb | 5 + spec/rails_helper.rb | 2 + test/contexts.rb | 23 ++ test/controllers/articles_controller_test.rb | 23 ++ test/factories/articles.rb | 10 + test/factories/user_articles.rb | 7 + test/factories/users.rb | 10 + test/models/article_test.rb | 32 ++ test/models/user_article_test.rb | 10 + test/models/user_test.rb | 11 + test/sets/articles.rb | 17 + test/sets/user_articles.rb | 20 + test/sets/users.rb | 17 + test/test_helper.rb | 5 + 64 files changed, 1290 insertions(+), 127 deletions(-) create mode 100644 app/controllers/articles_controller.rb create mode 100644 app/controllers/services/base.rb create mode 100644 app/controllers/user_articles_controller.rb create mode 100644 app/controllers/users_controller.rb create mode 100644 app/models/article.rb create mode 100644 app/models/user_article.rb create mode 100644 app/views/articles/index.html.erb create mode 100644 app/views/articles/liked_articles.html.erb create mode 100644 app/views/articles/show.html.erb create mode 100644 app/views/devise/confirmations/new.html.erb create mode 100644 app/views/devise/mailer/confirmation_instructions.html.erb create mode 100644 app/views/devise/mailer/email_changed.html.erb create mode 100644 app/views/devise/mailer/password_change.html.erb create mode 100644 app/views/devise/mailer/reset_password_instructions.html.erb create mode 100644 app/views/devise/mailer/unlock_instructions.html.erb create mode 100644 app/views/devise/passwords/edit.html.erb create mode 100644 app/views/devise/passwords/new.html.erb create mode 100644 app/views/devise/registrations/edit.html.erb create mode 100644 app/views/devise/registrations/new.html.erb create mode 100644 app/views/devise/sessions/new.html.erb create mode 100644 app/views/devise/shared/_error_messages.html.erb create mode 100644 app/views/devise/shared/_links.html.erb create mode 100644 app/views/devise/unlocks/new.html.erb create mode 100644 app/views/liked_articles/index.html.erb create mode 100644 app/views/partials/_add_new_object.html.erb create mode 100644 app/views/partials/_flash.html.erb create mode 100644 app/views/partials/_footer.html.erb create mode 100644 app/views/partials/_nav.html.erb create mode 100644 app/views/partials/_nav_guest.html.erb create mode 100644 app/views/partials/_object_options.html.erb create mode 100644 app/views/partials/_quick_links.html.erb create mode 100644 app/views/partials/_structure1.html.erb create mode 100644 app/views/partials/_structure2.html.erb create mode 100644 app/views/partials/_topbar.html.erb create mode 100644 app/views/users/_user.html.erb create mode 100644 app/views/users/index.html.erb create mode 100644 db/migrate/20230524183141_create_articles.rb create mode 100644 db/migrate/20230524184051_create_user_articles.rb create mode 100644 lib/hackernews/connection.rb create mode 100644 lib/hackernews/request.rb create mode 100644 spec/models/article_spec.rb create mode 100644 spec/models/user_article_spec.rb create mode 100644 test/contexts.rb create mode 100644 test/controllers/articles_controller_test.rb create mode 100644 test/factories/articles.rb create mode 100644 test/factories/user_articles.rb create mode 100644 test/factories/users.rb create mode 100644 test/models/article_test.rb create mode 100644 test/models/user_article_test.rb create mode 100644 test/models/user_test.rb create mode 100644 test/sets/articles.rb create mode 100644 test/sets/user_articles.rb create mode 100644 test/sets/users.rb diff --git a/Gemfile b/Gemfile index 5a8ffc43..513ca7fd 100644 --- a/Gemfile +++ b/Gemfile @@ -2,8 +2,8 @@ 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 'byebug', platforms: [:mri, :mingw, :x64_mingw], group: [:development, :test] +# gem 'capybara', group: [:development, :test] gem 'coffee-rails' gem 'devise' gem 'jbuilder' @@ -20,3 +20,33 @@ gem 'turbolinks' gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] gem 'uglifier' gem 'web-console', group: :development +gem 'rest-client' +gem 'dotenv-rails' +gem 'faraday' +gem 'bcrypt' + +group :development, :test do + # Call 'byebug' anywhere in the code to stop execution and get a debugger console + gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] + # Adds support for Capybara system testing and webdrivers + gem 'capybara' + gem 'webdrivers' + gem 'better_errors' + gem 'binding_of_caller' + gem 'meta_request' + gem 'hirb' + gem 'faker' + gem 'populator' + gem 'factory_bot_rails' + gem 'simplecov' + gem 'shoulda' + gem 'shoulda-matchers', '2.8.0' + gem 'minitest' + gem 'minitest-rails' + gem 'minitest-reporters' + gem 'rails-controller-testing' + gem 'database_cleaner', '1.8.5' + gem 'launchy', '2.5.0' + gem 'bigdecimal', '1.3.0' + end + diff --git a/Gemfile.lock b/Gemfile.lock index 14ec6457..f3d291a9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,78 +1,86 @@ GEM remote: https://rubygems.org/ specs: - actioncable (7.0.4) - actionpack (= 7.0.4) - activesupport (= 7.0.4) + actioncable (7.0.5) + actionpack (= 7.0.5) + activesupport (= 7.0.5) 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.5) + actionpack (= 7.0.5) + activejob (= 7.0.5) + activerecord (= 7.0.5) + activestorage (= 7.0.5) + activesupport (= 7.0.5) 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.5) + actionpack (= 7.0.5) + actionview (= 7.0.5) + activejob (= 7.0.5) + activesupport (= 7.0.5) 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.5) + actionview (= 7.0.5) + activesupport (= 7.0.5) + 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.5) + actionpack (= 7.0.5) + activerecord (= 7.0.5) + activestorage (= 7.0.5) + activesupport (= 7.0.5) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.0.4) - activesupport (= 7.0.4) + actionview (7.0.5) + activesupport (= 7.0.5) 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.5) + activesupport (= 7.0.5) 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.5) + activesupport (= 7.0.5) + activerecord (7.0.5) + activemodel (= 7.0.5) + activesupport (= 7.0.5) + activestorage (7.0.5) + actionpack (= 7.0.5) + activejob (= 7.0.5) + activerecord (= 7.0.5) + activesupport (= 7.0.5) marcel (~> 1.0) mini_mime (>= 1.1.0) - activesupport (7.0.4) + activesupport (7.0.5) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) - addressable (2.8.1) + addressable (2.8.4) public_suffix (>= 2.0.2, < 6.0) + ansi (1.5.0) bcrypt (3.1.18) + better_errors (2.10.0) + erubi (>= 1.0.0) + rack (>= 0.9.0) + rouge (>= 1.0.0) + bigdecimal (1.3.0) bindex (0.8.1) + binding_of_caller (1.0.0) + debug_inspector (>= 0.0.1) builder (3.2.4) byebug (11.1.3) - capybara (3.37.1) + capybara (3.39.1) addressable matrix mini_mime (>= 0.1.3) @@ -81,7 +89,6 @@ GEM 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,94 +97,140 @@ GEM coffee-script-source execjs coffee-script-source (1.12.2) - concurrent-ruby (1.1.10) + concurrent-ruby (1.2.2) crass (1.0.6) - devise (4.8.1) + database_cleaner (1.8.5) + date (3.3.3) + debug_inspector (1.1.0) + devise (4.9.2) 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) + docile (1.4.0) + domain_name (0.5.20190701) + unf (>= 0.0.5, < 1.0.0) + dotenv (2.8.1) + dotenv-rails (2.8.1) + dotenv (= 2.8.1) + railties (>= 3.2) + erubi (1.12.0) execjs (2.8.1) + factory_bot (6.2.1) + activesupport (>= 5.0.0) + factory_bot_rails (6.2.0) + factory_bot (~> 6.2.0) + railties (>= 5.0.0) + faker (3.2.0) + i18n (>= 1.8.11, < 2) + faraday (2.7.5) + faraday-net_http (>= 2.0, < 3.1) + ruby2_keywords (>= 0.0.4) + faraday-net_http (3.0.2) ffi (1.15.5) - globalid (1.0.0) + globalid (1.1.0) activesupport (>= 5.0) - i18n (1.12.0) + hirb (0.7.3) + http-accept (1.7.0) + http-cookie (1.0.5) + domain_name (~> 0.5) + i18n (1.13.0) concurrent-ruby (~> 1.0) jbuilder (2.11.5) actionview (>= 5.0.0) activesupport (>= 5.0.0) - listen (3.7.1) + launchy (2.5.0) + addressable (~> 2.7) + listen (3.8.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) - loofah (2.19.0) + loofah (2.21.3) 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) + net-imap + net-pop + net-smtp marcel (1.0.2) matrix (0.4.2) + meta_request (0.7.4) + rack-contrib (>= 1.1, < 3) + railties (>= 3.0.0, < 7.1) method_source (1.0.0) + mime-types (3.4.1) + mime-types-data (~> 3.2015) + mime-types-data (3.2023.0218.1) mini_mime (1.1.2) - mini_portile2 (2.8.0) - minitest (5.16.3) - net-imap (0.2.3) - digest + mini_portile2 (2.8.2) + minitest (5.18.0) + minitest-rails (7.0.0) + minitest (~> 5.10) + railties (~> 7.0.0) + minitest-reporters (1.6.0) + ansi + builder + minitest (>= 5.0) + ruby-progressbar + net-imap (0.3.4) + date net-protocol - strscan - net-pop (0.1.1) - digest + net-pop (0.1.2) net-protocol + net-protocol (0.2.1) timeout - net-protocol (0.1.3) - timeout - net-smtp (0.3.1) - digest + net-smtp (0.3.3) net-protocol - timeout - nio4r (2.5.8) - nokogiri (1.13.8) - mini_portile2 (~> 2.8.0) + netrc (0.11.0) + nio4r (2.5.9) + nokogiri (1.15.2) + mini_portile2 (~> 2.8.2) racc (~> 1.4) orm_adapter (0.5.0) - pg (1.4.3) - pry (0.14.1) + pg (1.5.3) + populator (1.0.0) + 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) + public_suffix (5.0.1) + puma (6.2.2) nio4r (~> 2.0) - racc (1.6.0) - rack (2.2.4) - rack-test (2.0.2) + racc (1.6.2) + rack (2.2.7) + rack-contrib (2.3.0) + rack (~> 2.0) + 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.5) + actioncable (= 7.0.5) + actionmailbox (= 7.0.5) + actionmailer (= 7.0.5) + actionpack (= 7.0.5) + actiontext (= 7.0.5) + actionview (= 7.0.5) + activejob (= 7.0.5) + activemodel (= 7.0.5) + activerecord (= 7.0.5) + activestorage (= 7.0.5) + activesupport (= 7.0.5) bundler (>= 1.15.0) - railties (= 7.0.4) + railties (= 7.0.5) + rails-controller-testing (1.0.5) + actionpack (>= 5.0.1.rc1) + actionview (>= 5.0.1.rc1) + activesupport (>= 5.0.1.rc1) rails-dom-testing (2.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) + rails-html-sanitizer (1.5.0) + loofah (~> 2.19, >= 2.19.1) + railties (7.0.5) + actionpack (= 7.0.5) + activesupport (= 7.0.5) method_source rake (>= 12.2) thor (~> 1.0) @@ -186,28 +239,36 @@ GEM 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) + regexp_parser (2.8.0) + responders (3.1.0) + actionpack (>= 5.2) + railties (>= 5.2) + rest-client (2.1.0) + http-accept (>= 1.7.0, < 2.0) + http-cookie (>= 1.0.2, < 2.0) + mime-types (>= 1.16, < 4.0) + netrc (~> 0.8) rexml (3.2.5) - rspec-core (3.11.0) - rspec-support (~> 3.11.0) - rspec-expectations (3.11.1) + rouge (4.1.1) + rspec-core (3.12.2) + rspec-support (~> 3.12.0) + rspec-expectations (3.12.3) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.11.0) - rspec-mocks (3.11.1) + rspec-support (~> 3.12.0) + rspec-mocks (3.12.5) 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) + rspec-support (~> 3.12.0) + rspec-rails (6.0.2) + actionpack (>= 6.1) + activesupport (>= 6.1) + railties (>= 6.1) + rspec-core (~> 3.12) + rspec-expectations (~> 3.12) + rspec-mocks (~> 3.12) + rspec-support (~> 3.12) + rspec-support (3.12.0) + ruby-progressbar (1.13.0) + ruby2_keywords (0.0.5) rubyzip (2.3.2) sass-rails (6.0.0) sassc-rails (~> 2.1, >= 2.1.1) @@ -219,30 +280,43 @@ GEM sprockets (> 3.0) sprockets-rails tilt - selenium-webdriver (4.4.0) - childprocess (>= 0.5, < 5.0) + selenium-webdriver (4.9.1) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) - spring (4.1.0) - sprockets (4.1.1) + shoulda (3.5.0) + shoulda-context (~> 1.0, >= 1.0.1) + shoulda-matchers (>= 1.4.1, < 3.0) + shoulda-context (1.2.2) + shoulda-matchers (2.8.0) + activesupport (>= 3.0.0) + simplecov (0.22.0) + docile (~> 1.1) + simplecov-html (~> 0.11) + simplecov_json_formatter (~> 0.1) + simplecov-html (0.12.3) + simplecov_json_formatter (0.1.4) + spring (4.1.1) + sprockets (4.2.0) concurrent-ruby (~> 1.0) - rack (> 1, < 3) + rack (>= 2.2.4, < 4) 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) + thor (1.2.2) + tilt (2.1.0) + timeout (0.3.2) 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) + unf (0.1.4) + unf_ext + unf_ext (0.0.8.2) warden (1.2.9) rack (>= 2.0.9) web-console (4.2.0) @@ -250,36 +324,62 @@ GEM activemodel (>= 6.0.0) bindex (>= 0.4.0) railties (>= 6.0.0) + webdrivers (5.2.0) + nokogiri (~> 1.6) + rubyzip (>= 1.3.0) + selenium-webdriver (~> 4.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) + zeitwerk (2.6.8) PLATFORMS ruby DEPENDENCIES + bcrypt + better_errors + bigdecimal (= 1.3.0) + binding_of_caller byebug capybara coffee-rails + database_cleaner (= 1.8.5) devise + dotenv-rails + factory_bot_rails + faker + faraday + hirb jbuilder + launchy (= 2.5.0) listen + meta_request + minitest + minitest-rails + minitest-reporters pg + populator pry-rails puma rails (~> 7.0.3) + rails-controller-testing + rest-client rspec-rails sass-rails selenium-webdriver + shoulda + shoulda-matchers (= 2.8.0) + simplecov spring turbolinks tzinfo-data uglifier web-console + webdrivers RUBY VERSION ruby 3.1.2p20 diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 1c07694e..3f10acdb 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,3 +1,19 @@ class ApplicationController < ActionController::Base protect_from_forgery with: :exception + + before_action :configure_permitted_parameters, if: :devise_controller? + + protected + + def configure_permitted_parameters + # registration_params = [:first_name, :last_name, :email, :password, ] + + devise_parameter_sanitizer.permit(:sign_up, keys: [:first_name, :last_name, :email, :password]) + + # if params[:action] == 'create' + # devise_parameter_sanitizer.for(:sign_up) do + # |u| u.permit(registration_params) + # end + # end end + end end diff --git a/app/controllers/articles_controller.rb b/app/controllers/articles_controller.rb new file mode 100644 index 00000000..4864576f --- /dev/null +++ b/app/controllers/articles_controller.rb @@ -0,0 +1,52 @@ +class ArticlesController < ApplicationController + + def index + @articles = Rails.cache.fetch(["top_stories_details"], expires_in: 4.hours) do + top_stories + end + end + + def top_stories + url = "https://hacker-news.firebaseio.com/v0/topstories.json" + response = RestClient.get(url) + articles_ids = JSON.parse(RestClient.get("https://hacker-news.firebaseio.com/v0/topstories.json")).slice(0, 100) + res = articles_ids.map { |article_id| get_details(article_id) }.to_a + # sort top stories by score + return res.sort_by {|art| art.score }.reverse + end + + def liked_articles + # sort liked articles by how many likes they have + @articles = (Article.has_likes.alphabetical.to_a).sort_by{|art| art.user_articles.count}.reverse + end + + def get_details(article_id) + response = RestClient.get("https://hacker-news.firebaseio.com/v0/item/" + article_id.to_s + ".json") + @article = Article.new() + @article.title = JSON.parse(response)['title'] + @article.author = JSON.parse(response)['by'] + @article.score = JSON.parse(response)['score'] + @article.url = JSON.parse(response)['url'] + @article.article_id = article_id + + + return @article + end + + def show + @article = Article.find_by(:article_id => params[:id]) + + # set up @users_toggle for whether to show the like button to user (user cannot like same article multiple times) + if !@article.user_articles.empty? + @users_toggle = @article.user_articles.map{ |ua| ua.user } + end + end + + def new + @article = Article.new + end + + def edit + end + +end \ No newline at end of file diff --git a/app/controllers/services/base.rb b/app/controllers/services/base.rb new file mode 100644 index 00000000..e3ee74fd --- /dev/null +++ b/app/controllers/services/base.rb @@ -0,0 +1,7 @@ +class Article + attr_accessor :id, :title, :author + + def initialize(id) + @id = id + end +end \ No newline at end of file diff --git a/app/controllers/user_articles_controller.rb b/app/controllers/user_articles_controller.rb new file mode 100644 index 00000000..5f9498b8 --- /dev/null +++ b/app/controllers/user_articles_controller.rb @@ -0,0 +1,52 @@ +class UserArticlesController < ApplicationController + + def like + @user_article = UserArticle.new() + + # only save article if it hasn't been liked before + @article = Article.find_by(:article_id => params[:id]) + + if @article.nil? + @article = Article.new() + response = RestClient.get("https://hacker-news.firebaseio.com/v0/item/" + params[:id].to_s + ".json") + @article.title = JSON.parse(response)['title'] + @article.author = JSON.parse(response)['by'] + @article.score = JSON.parse(response)['score'] + @article.url = JSON.parse(response)['url'] + @article.article_id = params[:id] + if @article.save + flash[:notice] = "Successfully liked the article" + + @user_article.article = @article + @user_article.user = current_user + if @user_article.save + flash[:notice] = "Successfully liked the article" + redirect_to article_path(@article.article_id) + else + redirect_to action: "index" + end + end + else + # if the article already exists + @user_article.article = @article + @user_article.user = current_user + if @user_article.save + flash[:notice] = "Successfully liked the article" + redirect_to article_path(@article.article_id) + else + redirect_to action: "index" + end + end + + end + + def unlike + @article = Article.find_by(:article_id => params[:id]) + @user_article = UserArticle.find_by(:user => current_user, :article => @article) + @user_article.destroy + flash[:notice] = "Successfully unliked article" + redirect_to article_path(@article.article_id) + end + + +end \ No newline at end of file diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb new file mode 100644 index 00000000..02205ace --- /dev/null +++ b/app/controllers/users_controller.rb @@ -0,0 +1,16 @@ +# require 'dotenv' +# require 'json' +# Dotenv.load + +class UsersController < ApplicationController + + def destroy + @user = User.find(params[:id]) + @user.destroy + + if @user.destroy + redirect_to root_url, notice: "User deleted." + end + end + +end \ No newline at end of file diff --git a/app/models/article.rb b/app/models/article.rb new file mode 100644 index 00000000..d9f59fbb --- /dev/null +++ b/app/models/article.rb @@ -0,0 +1,19 @@ +class Article < ApplicationRecord + # Relationships + has_many :user_articles + has_many :users, through: :user_articles + + # Scopes + scope :alphabetical, -> { order('title') } + scope :has_likes, -> { Article.includes(:user_articles).where.not(user_articles: { article_id: nil }) } + + # Validations + validates_presence_of :article_id, :title, :author, :score, :url + validates_numericality_of :score + + def to_sym + :read_only + end + + +end diff --git a/app/models/user.rb b/app/models/user.rb index b2091f9a..e58523a8 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -3,4 +3,13 @@ class User < ApplicationRecord # :confirmable, :lockable, :timeoutable and :omniauthable devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable + + # Relationships + has_many :user_articles + has_many :articles, through: :user_articles + + # Validations + validates_presence_of :first_name, :last_name, :email, :password + end + diff --git a/app/models/user_article.rb b/app/models/user_article.rb new file mode 100644 index 00000000..235d2797 --- /dev/null +++ b/app/models/user_article.rb @@ -0,0 +1,6 @@ +class UserArticle < ApplicationRecord + belongs_to :user + belongs_to :article + + validates_presence_of :user_id, :article_id +end diff --git a/app/views/articles/index.html.erb b/app/views/articles/index.html.erb new file mode 100644 index 00000000..348a797f --- /dev/null +++ b/app/views/articles/index.html.erb @@ -0,0 +1,55 @@ +

Welcome to Top News

+ +<%= link_to "liked articles", liked_articles_url%> + + + + + + \ + + + + +<% for article in @articles %> + + + + + + + + + + + <% if user_signed_in? %> + + <%else%> + + <% end -%> + + + + <% end %> +
TitleAuthorScoreLike?
<%= link_to article.title, article.url %><%= article.author %><%= article.score %> <%if !article.user_articles&.present?%> + <%= link_to 'like',like_path(id:article.article_id), method: :patch%> + <%else%> + <%= link_to 'unlike',unlike_path(id:article.article_id), method: :patch%> + <% end -%> please sign in to like articles + +
diff --git a/app/views/articles/liked_articles.html.erb b/app/views/articles/liked_articles.html.erb new file mode 100644 index 00000000..d2f177e9 --- /dev/null +++ b/app/views/articles/liked_articles.html.erb @@ -0,0 +1,35 @@ +

Welcome to Top News

+

Liked Articles

+ +<%= link_to "top stories", articles_url%> + + + + \ + + + + +<% for article in @articles %> + + + + + + + + + + <% article.user_articles %> + + + + + <% end %> +
TitleAuthorScoreLikes
<%= link_to article.title, article_path(article.article_id) %><%= article.author %><%= article.score %> <%if !article.user_articles&.present?%> + <%= 0 %> + <%else%> + <%= article.user_articles.map { |ua| ua.user }.count %> + <% end -%> + +
diff --git a/app/views/articles/show.html.erb b/app/views/articles/show.html.erb new file mode 100644 index 00000000..ce9c2492 --- /dev/null +++ b/app/views/articles/show.html.erb @@ -0,0 +1,31 @@ +

<%= @article.title %>

+ +<%= link_to "top stories", articles_url%> +<%= link_to "liked articles", liked_articles_url%> +<%= link_to "read article", @article.url%> + +<% if user_signed_in? %> + <% if !(@users_toggle.nil?) %> + <% if !(@users_toggle.include?(current_user)) %> + <%= link_to 'like',like_path(id:@article.article_id), method: :patch%> + <%else%> + <%= link_to 'unlike',unlike_path(id:@article.article_id), method: :patch%> + <% end %> + <%else%> + <%= link_to 'like',like_path(id:@article.article_id), method: :patch%> + <% end %> +<%else%> +

please sign in to like articles

+ <% end %> + +

+

    + <% if !@article.user_articles.empty? %> + <% for user_article in @article.user_articles %> +
  • <%= user_article.user.first_name %>
  • + <% end %> + <%else%> +
  • No Likes
  • + <% end -%> +
+

diff --git a/app/views/devise/confirmations/new.html.erb b/app/views/devise/confirmations/new.html.erb new file mode 100644 index 00000000..b12dd0cb --- /dev/null +++ b/app/views/devise/confirmations/new.html.erb @@ -0,0 +1,16 @@ +

Resend confirmation instructions

+ +<%= form_for(resource, as: resource_name, url: confirmation_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", value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email) %> +
+ +
+ <%= f.submit "Resend confirmation instructions" %> +
+<% end %> + +<%= render "devise/shared/links" %> diff --git a/app/views/devise/mailer/confirmation_instructions.html.erb b/app/views/devise/mailer/confirmation_instructions.html.erb new file mode 100644 index 00000000..dc55f64f --- /dev/null +++ b/app/views/devise/mailer/confirmation_instructions.html.erb @@ -0,0 +1,5 @@ +

Welcome <%= @email %>!

+ +

You can confirm your account email through the link below:

+ +

<%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>

diff --git a/app/views/devise/mailer/email_changed.html.erb b/app/views/devise/mailer/email_changed.html.erb new file mode 100644 index 00000000..32f4ba80 --- /dev/null +++ b/app/views/devise/mailer/email_changed.html.erb @@ -0,0 +1,7 @@ +

Hello <%= @email %>!

+ +<% if @resource.try(:unconfirmed_email?) %> +

We're contacting you to notify you that your email is being changed to <%= @resource.unconfirmed_email %>.

+<% else %> +

We're contacting you to notify you that your email has been changed to <%= @resource.email %>.

+<% end %> diff --git a/app/views/devise/mailer/password_change.html.erb b/app/views/devise/mailer/password_change.html.erb new file mode 100644 index 00000000..b41daf47 --- /dev/null +++ b/app/views/devise/mailer/password_change.html.erb @@ -0,0 +1,3 @@ +

Hello <%= @resource.email %>!

+ +

We're contacting you to notify you that your password has been changed.

diff --git a/app/views/devise/mailer/reset_password_instructions.html.erb b/app/views/devise/mailer/reset_password_instructions.html.erb new file mode 100644 index 00000000..f667dc12 --- /dev/null +++ b/app/views/devise/mailer/reset_password_instructions.html.erb @@ -0,0 +1,8 @@ +

Hello <%= @resource.email %>!

+ +

Someone has requested a link to change your password. You can do this through the link below.

+ +

<%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %>

+ +

If you didn't request this, please ignore this email.

+

Your password won't change until you access the link above and create a new one.

diff --git a/app/views/devise/mailer/unlock_instructions.html.erb b/app/views/devise/mailer/unlock_instructions.html.erb new file mode 100644 index 00000000..41e148bf --- /dev/null +++ b/app/views/devise/mailer/unlock_instructions.html.erb @@ -0,0 +1,7 @@ +

Hello <%= @resource.email %>!

+ +

Your account has been locked due to an excessive number of unsuccessful sign in attempts.

+ +

Click the link below to unlock your account:

+ +

<%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %>

diff --git a/app/views/devise/passwords/edit.html.erb b/app/views/devise/passwords/edit.html.erb new file mode 100644 index 00000000..5fbb9ff0 --- /dev/null +++ b/app/views/devise/passwords/edit.html.erb @@ -0,0 +1,25 @@ +

Change your password

+ +<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %> + <%= render "devise/shared/error_messages", resource: resource %> + <%= f.hidden_field :reset_password_token %> + +
+ <%= f.label :password, "New password" %>
+ <% if @minimum_password_length %> + (<%= @minimum_password_length %> characters minimum)
+ <% end %> + <%= f.password_field :password, autofocus: true, autocomplete: "new-password" %> +
+ +
+ <%= f.label :password_confirmation, "Confirm new password" %>
+ <%= f.password_field :password_confirmation, autocomplete: "new-password" %> +
+ +
+ <%= f.submit "Change my password" %> +
+<% end %> + +<%= render "devise/shared/links" %> diff --git a/app/views/devise/passwords/new.html.erb b/app/views/devise/passwords/new.html.erb new file mode 100644 index 00000000..9b486b81 --- /dev/null +++ b/app/views/devise/passwords/new.html.erb @@ -0,0 +1,16 @@ +

Forgot your password?

+ +<%= form_for(resource, as: resource_name, url: password_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 "Send me reset password instructions" %> +
+<% end %> + +<%= render "devise/shared/links" %> diff --git a/app/views/devise/registrations/edit.html.erb b/app/views/devise/registrations/edit.html.erb new file mode 100644 index 00000000..b82e3365 --- /dev/null +++ b/app/views/devise/registrations/edit.html.erb @@ -0,0 +1,43 @@ +

Edit <%= resource_name.to_s.humanize %>

+ +<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %> + <%= render "devise/shared/error_messages", resource: resource %> + +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email" %> +
+ + <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %> +
Currently waiting confirmation for: <%= resource.unconfirmed_email %>
+ <% end %> + +
+ <%= f.label :password %> (leave blank if you don't want to change it)
+ <%= f.password_field :password, autocomplete: "new-password" %> + <% if @minimum_password_length %> +
+ <%= @minimum_password_length %> characters minimum + <% end %> +
+ +
+ <%= f.label :password_confirmation %>
+ <%= f.password_field :password_confirmation, autocomplete: "new-password" %> +
+ +
+ <%= f.label :current_password %> (we need your current password to confirm your changes)
+ <%= f.password_field :current_password, autocomplete: "current-password" %> +
+ +
+ <%= f.submit "Update" %> +
+<% end %> + +

Cancel my account

+ +
Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?", turbo_confirm: "Are you sure?" }, method: :delete %>
+ +<%= link_to "Back", :back %> diff --git a/app/views/devise/registrations/new.html.erb b/app/views/devise/registrations/new.html.erb new file mode 100644 index 00000000..cf8e2ca7 --- /dev/null +++ b/app/views/devise/registrations/new.html.erb @@ -0,0 +1,39 @@ +

Sign up

+ +<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %> + <%= render "devise/shared/error_messages", resource: resource %> + +
+ <%= f.label :first_name %>
+ <%= f.text_field :first_name, autofocus: true, autocomplete: "last name" %> +
+ +
+ <%= f.label :last_name %>
+ <%= f.text_field :last_name, autofocus: true, autocomplete: "last name" %> +
+ +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email" %> +
+ +
+ <%= f.label :password %> + <% if @minimum_password_length %> + (<%= @minimum_password_length %> characters minimum) + <% end %>
+ <%= f.password_field :password, autocomplete: "new-password" %> +
+ +
+ <%= f.label :password_confirmation %>
+ <%= f.password_field :password_confirmation, autocomplete: "new-password" %> +
+ +
+ <%= f.submit "Sign up" %> +
+<% end %> + +<%= render "devise/shared/links" %> diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb new file mode 100644 index 00000000..5ede9648 --- /dev/null +++ b/app/views/devise/sessions/new.html.erb @@ -0,0 +1,26 @@ +

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" %> +
+ + <% if devise_mapping.rememberable? %> +
+ <%= f.check_box :remember_me %> + <%= f.label :remember_me %> +
+ <% end %> + +
+ <%= f.submit "Log in" %> +
+<% end %> + +<%= render "devise/shared/links" %> 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/liked_articles/index.html.erb b/app/views/liked_articles/index.html.erb new file mode 100644 index 00000000..c79458db --- /dev/null +++ b/app/views/liked_articles/index.html.erb @@ -0,0 +1,36 @@ +

Welcome to Top News

+

Liked Articles

+ + + \ + + + + +<% for article in @articles %> + + + + + + + + <% article.user_articles %> + + + + + + + <% end %> +
TitleAuthorLikesLike?
<%= link_to article.title, article_path(article.article_id) %><%= article.author %> <%if !article.user_articles&.present?%> + <%= 0 %> + <%else%> + <%= article.user_articles.map { |ua| ua.user }.count %> + <% end -%> <%if !(current_user in article.user_articles)%> + <%= link_to 'like',like_path(id:article.article_id), method: :patch%> + <%else%> + <%= link_to 'deactivate',unlike_path(id:article.article_id), method: :patch%> + <% end -%> + +
diff --git a/app/views/pages/home.html.erb b/app/views/pages/home.html.erb index 8bfd8294..0b409157 100644 --- a/app/views/pages/home.html.erb +++ b/app/views/pages/home.html.erb @@ -1 +1,9 @@

Welcome to Top News

+ +

+

    + <% for article in @articles %> +
  • <%= article %>
  • + <% end %> +
+

diff --git a/app/views/partials/_add_new_object.html.erb b/app/views/partials/_add_new_object.html.erb new file mode 100644 index 00000000..20afd496 --- /dev/null +++ b/app/views/partials/_add_new_object.html.erb @@ -0,0 +1,4 @@ + +<%= link_to(eval("new_#{model_name}_path"), class: "waves-effect waves-light btn") do %> + Add a New <%= model_name.capitalize %> +<% end %> \ No newline at end of file diff --git a/app/views/partials/_flash.html.erb b/app/views/partials/_flash.html.erb new file mode 100644 index 00000000..c1ab49aa --- /dev/null +++ b/app/views/partials/_flash.html.erb @@ -0,0 +1,6 @@ +<% flash.each do |name, msg| %> +
"> + + <%= msg %> +
+<% end %> \ No newline at end of file diff --git a/app/views/partials/_footer.html.erb b/app/views/partials/_footer.html.erb new file mode 100644 index 00000000..c25773d9 --- /dev/null +++ b/app/views/partials/_footer.html.erb @@ -0,0 +1,15 @@ +

 

+ + \ No newline at end of file diff --git a/app/views/partials/_nav.html.erb b/app/views/partials/_nav.html.erb new file mode 100644 index 00000000..9d7bf24c --- /dev/null +++ b/app/views/partials/_nav.html.erb @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/app/views/partials/_nav_guest.html.erb b/app/views/partials/_nav_guest.html.erb new file mode 100644 index 00000000..b759c491 --- /dev/null +++ b/app/views/partials/_nav_guest.html.erb @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/app/views/partials/_object_options.html.erb b/app/views/partials/_object_options.html.erb new file mode 100644 index 00000000..f6c6c3a3 --- /dev/null +++ b/app/views/partials/_object_options.html.erb @@ -0,0 +1,17 @@ +<% opt = "#{object.respond_to?(:name) ? object.name : model_name.capitalize }" %> + +
+ <%= link_to(eval("#{model_name.pluralize}_path"), class: 'waves-effect waves-light btn') do %> +  Display All <%# model_name.pluralize.capitalize %> + <% end %> +    + + <%= link_to(eval("edit_#{model_name}_path(object)"), class: 'waves-effect waves-light btn') do %> +  <%= "Edit #{opt}" %> + <% end %> +    + + <%= link_to(eval("#{model_name}_path(object)"), method: 'delete', data: {confirm: "Are you sure you want to permanently delete #{opt}"}, class: 'waves-effect waves-light btn btn-alert') do %> +  <%= "Delete #{opt}" %> + <% end %> +
\ No newline at end of file diff --git a/app/views/partials/_quick_links.html.erb b/app/views/partials/_quick_links.html.erb new file mode 100644 index 00000000..8be20268 --- /dev/null +++ b/app/views/partials/_quick_links.html.erb @@ -0,0 +1,11 @@ +
+
+ Quick Links +
    +
  • <%= link_to 'About', about_path, :class => "collection-item" %>
  • +
  • <%= link_to 'Contact', contracted_books_path, :class => "collection-item" %>
  • +
  • <%= link_to 'New Proposals', proposed_books_path, :class => "collection-item" %>
  • +
  • <%= link_to 'Add a New Book', new_book_path, :class => "collection-item" %>
  • +
+
+
diff --git a/app/views/partials/_structure1.html.erb b/app/views/partials/_structure1.html.erb new file mode 100644 index 00000000..4d697a7c --- /dev/null +++ b/app/views/partials/_structure1.html.erb @@ -0,0 +1,15 @@ +
+
+ + <%= render partial: "#{primary}" %> + + <% unless secondary.nil? %> +

 

+ <%= render partial: "#{secondary}" %> + <% end %> +
+
+ + <%= render partial: "#{sidebar}" unless sidebar.blank? %> +
+
\ No newline at end of file diff --git a/app/views/partials/_structure2.html.erb b/app/views/partials/_structure2.html.erb new file mode 100644 index 00000000..9cd724fc --- /dev/null +++ b/app/views/partials/_structure2.html.erb @@ -0,0 +1,18 @@ +
+
+ + <%= render partial: "#{primary}" %> + + <% unless secondary.nil? %> +

 

+ <%= render partial: "#{secondary}" %> + <% end %> +
+
+ + <%= render partial: "#{sidebar}" unless sidebar.blank? %> +
+
+
+ <%= render :partial => "partials/object_options", locals: {model_name: model_name, object: object} %> +
\ No newline at end of file diff --git a/app/views/partials/_topbar.html.erb b/app/views/partials/_topbar.html.erb new file mode 100644 index 00000000..12675bbe --- /dev/null +++ b/app/views/partials/_topbar.html.erb @@ -0,0 +1,11 @@ +
+
+ <%= link_to(image_tag('logos/science_logo_3.png'), home_path) %> +
+
+

ScienceQuiz

+
+
+   +
+
diff --git a/app/views/users/_user.html.erb b/app/views/users/_user.html.erb new file mode 100644 index 00000000..94635c2d --- /dev/null +++ b/app/views/users/_user.html.erb @@ -0,0 +1,6 @@ +
+ <%= link_to user.name, user %> + <% if current_user.try(:admin?) && !current_user?(user) %> + <%= link_to "Destroy", admin_destroy_user_path(user), method: :delete, data: { confirm: "You sure?" } %> + <% end %> +
diff --git a/app/views/users/index.html.erb b/app/views/users/index.html.erb new file mode 100644 index 00000000..76cff339 --- /dev/null +++ b/app/views/users/index.html.erb @@ -0,0 +1,10 @@ +
+
+ USERS DIRECTORY + <%= render @users %> +
+ +
+ <%= will_paginate @users %> +
+
\ No newline at end of file diff --git a/config/environments/development.rb b/config/environments/development.rb index 5187e221..ebd0808d 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -4,7 +4,7 @@ # In the development environment your application's code is reloaded on # every request. This slows down response time but is perfect for development # since you don't have to restart the web server when you make code changes. - config.cache_classes = false + config.cache_classes = true # Do not eager load code on boot. config.eager_load = false 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/routes.rb b/config/routes.rb index c12ef082..213f72d1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,29 @@ Rails.application.routes.draw do + # devise_for :users + root to: "articles#index" + + patch 'user_articles/:id/like', to:'user_articles#like' , as: :like + patch 'user_articles/:id/unlike', to:'user_articles#unlike' , as: :unlike + devise_for :users - root to: 'pages#home' + devise_scope :user do + get '/users/sign_out' => 'devise/sessions#destroy' + post '/users/sign_up' => 'devise/registrations#create' + end + + # get '/articles', to: "articles#index" + resources :articles + + get '/liked_articles', to: "articles#liked_articles" + + resources :users + resources :sessions + + + + # get 'user/edit' => 'users#edit', :as => :edit_current_user + # get 'signup' => 'users#new', :as => :signup + # get 'login' => 'sessions#new', :as => :login + # get 'logout' => 'sessions#destroy', :as => :logout + end diff --git a/db/migrate/20230524183141_create_articles.rb b/db/migrate/20230524183141_create_articles.rb new file mode 100644 index 00000000..f806fb81 --- /dev/null +++ b/db/migrate/20230524183141_create_articles.rb @@ -0,0 +1,13 @@ +class CreateArticles < ActiveRecord::Migration[7.0] + def change + create_table :articles do |t| + t.integer :article_id + t.string :title + t.string :author + t.integer :score + t.string :url + + t.timestamps + end + end +end diff --git a/db/migrate/20230524184051_create_user_articles.rb b/db/migrate/20230524184051_create_user_articles.rb new file mode 100644 index 00000000..357ae65e --- /dev/null +++ b/db/migrate/20230524184051_create_user_articles.rb @@ -0,0 +1,10 @@ +class CreateUserArticles < ActiveRecord::Migration[7.0] + def change + create_table :user_articles do |t| + t.references :user, null: false, foreign_key: true + t.references :article, null: false, foreign_key: true + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index acc34f3b..5feeaed5 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,10 +10,29 @@ # # 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: 2023_05_24_184051) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" + create_table "articles", force: :cascade do |t| + t.integer "article_id" + t.string "title" + t.string "author" + t.integer "score" + t.string "url" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "user_articles", force: :cascade do |t| + t.bigint "user_id", null: false + t.bigint "article_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["article_id"], name: "index_user_articles_on_article_id" + t.index ["user_id"], name: "index_user_articles_on_user_id" + end + create_table "users", force: :cascade do |t| t.string "first_name" t.string "last_name" @@ -33,4 +52,6 @@ t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true end + add_foreign_key "user_articles", "articles" + add_foreign_key "user_articles", "users" end diff --git a/lib/hackernews/connection.rb b/lib/hackernews/connection.rb new file mode 100644 index 00000000..88b6196e --- /dev/null +++ b/lib/hackernews/connection.rb @@ -0,0 +1,14 @@ +require 'faraday' +require 'json' + +class Connection + BASE = 'https://hacker-news.firebaseio.com/v0/' + + def self.api + Faraday.new(url: BASE) do |faraday| + faraday.response :logger + faraday.adapter Faraday.default_adapter + # faraday.headers['Content-Type'] = 'application/json' + end + end +end \ No newline at end of file diff --git a/lib/hackernews/request.rb b/lib/hackernews/request.rb new file mode 100644 index 00000000..6d4308b1 --- /dev/null +++ b/lib/hackernews/request.rb @@ -0,0 +1,31 @@ +class Request + class << self + def where(resource_path, cache, query = {}, options = {}) + response, status = get_json(resource_path, cache, query) + status == 200 ? response : errors(response) + end + + def get(id, cache) + response, status = get_json(id, cache) + status == 200 ? response : errors(response) + end + + def errors(response) + error = { errors: { status: response["status"], message: response["message"] } } + response.merge(error) + end + + def get_json(root_path, cache, query = {}) + query_string = query.map{|k,v| "#{k}=#{v}"}.join("&") + path = query.empty?? root_path : "#{root_path}?#{query_string}" + response = Rails.cache.fetch(path, expires_in: cache[:expires_in], force: cache[:force]) do + api.get(path) + end + [JSON.parse(response.body), response.status] + end + + def api + Connection.api + end + end + end \ No newline at end of file diff --git a/spec/models/article_spec.rb b/spec/models/article_spec.rb new file mode 100644 index 00000000..632e8564 --- /dev/null +++ b/spec/models/article_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe Article, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/user_article_spec.rb b/spec/models/user_article_spec.rb new file mode 100644 index 00000000..a2dd0eee --- /dev/null +++ b/spec/models/user_article_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe UserArticle, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index bbe1ba57..e1108c2f 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -5,6 +5,8 @@ # Prevent database truncation if the environment is production abort("The Rails environment is running in production mode!") if Rails.env.production? require 'rspec/rails' +require 'shoulda/matchers' +require 'rspec/autorun' # Add additional requires below this line. Rails is not loaded until this point! # Requires supporting ruby files with custom matchers and macros, etc, in diff --git a/test/contexts.rb b/test/contexts.rb new file mode 100644 index 00000000..9728669f --- /dev/null +++ b/test/contexts.rb @@ -0,0 +1,23 @@ +# require needed files +require './test/sets/users' +require './test/sets/articles' +require './test/sets/user_articles' + + +module Contexts + # explicitly include all sets of contexts used for testing + include Contexts::Users + include Contexts::Articles + include Contexts::UserArticles + + def create_all + # Kernel.puts "Building context..." + create_users + # Kernel.puts "Created users" + create_articles + # Kernel.puts "Created articles" + create_user_articles + # Kernel.puts "Registered students on user_articles" + end + +end \ No newline at end of file diff --git a/test/controllers/articles_controller_test.rb b/test/controllers/articles_controller_test.rb new file mode 100644 index 00000000..9e9a2756 --- /dev/null +++ b/test/controllers/articles_controller_test.rb @@ -0,0 +1,23 @@ +require "test_helper" + +class ArticlesControllerTest < ActionDispatch::IntegrationTest + setup do + @article = FactoryBot.create(:article) + end + + test "should get index" do + get articles_path + assert_response :success + end + + test "should get liked articles" do + get liked_articles_path + assert_response :success + end + + test "should show organization" do + get article_path(@article.article_id) + assert_response :success + end + +end \ No newline at end of file diff --git a/test/factories/articles.rb b/test/factories/articles.rb new file mode 100644 index 00000000..90218037 --- /dev/null +++ b/test/factories/articles.rb @@ -0,0 +1,10 @@ +FactoryBot.define do + factory :article do + title { "How to Make it Big" } + author { "John Doe" } + score { 12345 } + url { "nytimes.com" } + article_id { 8863 } + end + end + \ No newline at end of file diff --git a/test/factories/user_articles.rb b/test/factories/user_articles.rb new file mode 100644 index 00000000..c033f2e3 --- /dev/null +++ b/test/factories/user_articles.rb @@ -0,0 +1,7 @@ +FactoryBot.define do + factory :user_article do + association :user + association :article + end + end + \ No newline at end of file diff --git a/test/factories/users.rb b/test/factories/users.rb new file mode 100644 index 00000000..89022f98 --- /dev/null +++ b/test/factories/users.rb @@ -0,0 +1,10 @@ +FactoryBot.define do + factory :user do + first_name { "Jane" } + last_name { "doe" } + email { "doe@gmail.com" } + password { "test123" } + password_confirmation { "test123" } + end + end + \ No newline at end of file diff --git a/test/models/article_test.rb b/test/models/article_test.rb new file mode 100644 index 00000000..bb07ef29 --- /dev/null +++ b/test/models/article_test.rb @@ -0,0 +1,32 @@ +require 'test_helper' + +class ArticleTest < ActiveSupport::TestCase + # Matchers + should have_many(:user_articles) + should have_many(:users).through(:user_articles) + + should validate_presence_of(:title) + should validate_presence_of(:author) + should validate_presence_of(:score) + should validate_presence_of(:url) + + should validate_numericality_of(:score) + + # Context + context "Given context" do + setup do + create_articles + create_users + create_user_articles + end + + should "list articles alphabetically" do + assert_equal [@nytimes, @washington_post, @time], Article.alphabetical + end + + should "list liked articles" do + assert_equal 2, Article.has_likes.count + assert_equal [@nytimes, @washington_post], Article.has_likes.alphabetical + end + end +end diff --git a/test/models/user_article_test.rb b/test/models/user_article_test.rb new file mode 100644 index 00000000..864f3c0f --- /dev/null +++ b/test/models/user_article_test.rb @@ -0,0 +1,10 @@ +require "test_helper" + +class UserArticleTest < ActiveSupport::TestCase + # Matchers + should belong_to(:user) + should belong_to(:article) + +# should validate_presence_of(:user_id) +# should validate_presence_of(:article_id) +end \ No newline at end of file diff --git a/test/models/user_test.rb b/test/models/user_test.rb new file mode 100644 index 00000000..7210e847 --- /dev/null +++ b/test/models/user_test.rb @@ -0,0 +1,11 @@ +require 'test_helper' + +class UserTest < ActiveSupport::TestCase + # Matchers + should have_many(:user_articles) + should have_many(:articles).through(:user_articles) + + should validate_presence_of(:first_name) + should validate_presence_of(:last_name) + should validate_presence_of(:password) +end diff --git a/test/sets/articles.rb b/test/sets/articles.rb new file mode 100644 index 00000000..5764cfad --- /dev/null +++ b/test/sets/articles.rb @@ -0,0 +1,17 @@ +module Contexts + module Articles + + def create_articles + @nytimes = FactoryBot.create(:article) + @washington_post = FactoryBot.create(:article, title: "Top Story", author: "John Doe", score: 500, url: "washingtonpost.com") + @time = FactoryBot.create(:article, title: "Top Story 2", author: "Janet Doe", score: 400, url: "time.com") + end + + def destroy_articles + @nytimes.destroy + @washington_post.destroy + @time.destroy + end + + end +end diff --git a/test/sets/user_articles.rb b/test/sets/user_articles.rb new file mode 100644 index 00000000..d4edbb1b --- /dev/null +++ b/test/sets/user_articles.rb @@ -0,0 +1,20 @@ +module Contexts + module UserArticles + + def create_user_articles + + @jane_like1 = FactoryBot.create(:user_article, user: @jane, article: @nytimes) + @lily_like1 = FactoryBot.create(:user_article, user: @lily, article: @nytimes) + @frank_like1 = FactoryBot.create(:user_article, user: @frank, article: @washington_post) + end + + + + def _user_articles + @jane_like1.destroy + @lily_like1.destroy + @frank_like1.destroy + end + + end + end \ No newline at end of file diff --git a/test/sets/users.rb b/test/sets/users.rb new file mode 100644 index 00000000..8dd14ea6 --- /dev/null +++ b/test/sets/users.rb @@ -0,0 +1,17 @@ +module Contexts + module Users + + def create_users + @jane = FactoryBot.create(:user) + @lily = FactoryBot.create(:user, first_name: "Lily", last_name: "Cao", email: "lily@yahoo.com", password: "123456", password_confirmation: "123456") + @frank = FactoryBot.create(:user, first_name: "Frank", last_name: "Cao", email: "frank@yahoo.com", password: "123456", password_confirmation: "123456") + end + + def destroy_users + @jane.destroy + @lily.destroy + @frank.destroy + end + + end + end \ No newline at end of file diff --git a/test/test_helper.rb b/test/test_helper.rb index 92e39b2d..9029fd47 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,10 +1,15 @@ ENV['RAILS_ENV'] ||= 'test' require File.expand_path('../../config/environment', __FILE__) require 'rails/test_help' +require "minitest" +require 'minitest/rails' +require 'minitest/reporters' +require 'contexts' class ActiveSupport::TestCase # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. fixtures :all + include Contexts # Add more helper methods to be used by all tests here... end From 51e954cc1b83761506fc736e5d532383740c9a5c Mon Sep 17 00:00:00 2001 From: Allison Cao Date: Thu, 25 May 2023 14:19:50 -0400 Subject: [PATCH 5/7] code further cleaned up, navbar added, bugs fixed --- app/controllers/articles_controller.rb | 37 +++++-------- app/controllers/user_articles_controller.rb | 11 +--- app/models/user_article.rb | 2 +- app/services/hacker_news.rb | 25 +++++++++ app/views/articles/_articles.html.erb | 36 +++++++++++++ app/views/articles/_liked_articles.html.erb | 30 +++++++++++ app/views/articles/index.html.erb | 57 ++------------------- app/views/articles/show.html.erb | 8 +-- app/views/layouts/application.html.erb | 1 + app/views/partials/_nav.html.erb | 17 ++++-- app/views/partials/_nav_guest.html.erb | 7 --- 11 files changed, 129 insertions(+), 102 deletions(-) create mode 100644 app/services/hacker_news.rb create mode 100644 app/views/articles/_articles.html.erb create mode 100644 app/views/articles/_liked_articles.html.erb delete mode 100644 app/views/partials/_nav_guest.html.erb diff --git a/app/controllers/articles_controller.rb b/app/controllers/articles_controller.rb index 4864576f..db32eeb7 100644 --- a/app/controllers/articles_controller.rb +++ b/app/controllers/articles_controller.rb @@ -2,17 +2,10 @@ class ArticlesController < ApplicationController def index @articles = Rails.cache.fetch(["top_stories_details"], expires_in: 4.hours) do - top_stories + HackerNews.get_top_stories() end - end - def top_stories - url = "https://hacker-news.firebaseio.com/v0/topstories.json" - response = RestClient.get(url) - articles_ids = JSON.parse(RestClient.get("https://hacker-news.firebaseio.com/v0/topstories.json")).slice(0, 100) - res = articles_ids.map { |article_id| get_details(article_id) }.to_a - # sort top stories by score - return res.sort_by {|art| art.score }.reverse + @liked_articles = (Article.has_likes.alphabetical.to_a).sort_by{|art| art.user_articles.count}.reverse end def liked_articles @@ -20,25 +13,19 @@ def liked_articles @articles = (Article.has_likes.alphabetical.to_a).sort_by{|art| art.user_articles.count}.reverse end - def get_details(article_id) - response = RestClient.get("https://hacker-news.firebaseio.com/v0/item/" + article_id.to_s + ".json") - @article = Article.new() - @article.title = JSON.parse(response)['title'] - @article.author = JSON.parse(response)['by'] - @article.score = JSON.parse(response)['score'] - @article.url = JSON.parse(response)['url'] - @article.article_id = article_id - - - return @article - end def show @article = Article.find_by(:article_id => params[:id]) - - # set up @users_toggle for whether to show the like button to user (user cannot like same article multiple times) - if !@article.user_articles.empty? - @users_toggle = @article.user_articles.map{ |ua| ua.user } + if @article.nil? # the article has not been liked and therefore is not saved + @article = HackerNews.get_details(params[:id]) + #raise "here" + else + #raise "here" + #@article = Article.find_by(:article_id => params[:id]) + # set up @users_toggle for whether to show the like button to user (user cannot like same article multiple times) + if !@article.user_articles.empty? + @users_toggle = @article.user_articles.map{ |ua| ua.user } + end end end diff --git a/app/controllers/user_articles_controller.rb b/app/controllers/user_articles_controller.rb index 5f9498b8..65c35da2 100644 --- a/app/controllers/user_articles_controller.rb +++ b/app/controllers/user_articles_controller.rb @@ -7,16 +7,9 @@ def like @article = Article.find_by(:article_id => params[:id]) if @article.nil? - @article = Article.new() - response = RestClient.get("https://hacker-news.firebaseio.com/v0/item/" + params[:id].to_s + ".json") - @article.title = JSON.parse(response)['title'] - @article.author = JSON.parse(response)['by'] - @article.score = JSON.parse(response)['score'] - @article.url = JSON.parse(response)['url'] - @article.article_id = params[:id] + @article = HackerNews.get_details(params[:id]) if @article.save - flash[:notice] = "Successfully liked the article" - + #flash[:notice] = "Successfully liked the article" @user_article.article = @article @user_article.user = current_user if @user_article.save diff --git a/app/models/user_article.rb b/app/models/user_article.rb index 235d2797..feea1fb5 100644 --- a/app/models/user_article.rb +++ b/app/models/user_article.rb @@ -2,5 +2,5 @@ class UserArticle < ApplicationRecord belongs_to :user belongs_to :article - validates_presence_of :user_id, :article_id + validates_presence_of :user_id, :article_id, uniqueness: true end diff --git a/app/services/hacker_news.rb b/app/services/hacker_news.rb new file mode 100644 index 00000000..4b78011a --- /dev/null +++ b/app/services/hacker_news.rb @@ -0,0 +1,25 @@ +require 'json' + +class HackerNews + def self.get_top_stories + url = "https://hacker-news.firebaseio.com/v0/topstories.json" + response = RestClient.get(url) + articles_ids = JSON.parse(RestClient.get("https://hacker-news.firebaseio.com/v0/topstories.json")).slice(0, 50) + res = articles_ids.map { |article_id| get_details(article_id) }.to_a + # sort top stories by score + res.sort_by {|art| art.score }.reverse + end + + def self.get_details(article_id) + response = RestClient.get("https://hacker-news.firebaseio.com/v0/item/" + article_id.to_s + ".json") + @article = Article.new() + @article.title = JSON.parse(response)['title'] + @article.author = JSON.parse(response)['by'] + @article.score = JSON.parse(response)['score'] + @article.url = JSON.parse(response)['url'] + @article.article_id = article_id + + return @article + end + +end \ No newline at end of file diff --git a/app/views/articles/_articles.html.erb b/app/views/articles/_articles.html.erb new file mode 100644 index 00000000..76726e17 --- /dev/null +++ b/app/views/articles/_articles.html.erb @@ -0,0 +1,36 @@ +

Welcome to Top News

+ + + + \ + + + + +<% for article in @articles %> + + + + + + + + + + + <% if user_signed_in? %> + + <%else%> + + <% end -%> + + + + <% end %> +
TitleAuthorScoreLike?
<%= link_to article.title, article_path(article.article_id) %><%= article.author %><%= article.score %> <%if !article.user_articles&.present?%> + <%= link_to 'like',like_path(id:article.article_id), method: :patch%> + <%else%> + <%= link_to 'unlike',unlike_path(id:article.article_id), method: :patch%> + <% end -%> please sign in to like articles + +
\ No newline at end of file diff --git a/app/views/articles/_liked_articles.html.erb b/app/views/articles/_liked_articles.html.erb new file mode 100644 index 00000000..2f844ff4 --- /dev/null +++ b/app/views/articles/_liked_articles.html.erb @@ -0,0 +1,30 @@ + + + \ + + + + +<% for article in @liked_articles %> + + + + + + + + + + <% article.user_articles %> + + + + + <% end %> +
TitleAuthorScoreLikes
<%= link_to article.title, article_path(article.article_id) %><%= article.author %><%= article.score %> <%if !article.user_articles&.present?%> + <%= 0 %> + <%else%> + <%= article.user_articles.map { |ua| ua.user }.count %> + <% end -%> + +
\ No newline at end of file diff --git a/app/views/articles/index.html.erb b/app/views/articles/index.html.erb index 348a797f..6fdc276e 100644 --- a/app/views/articles/index.html.erb +++ b/app/views/articles/index.html.erb @@ -1,55 +1,6 @@ -

Welcome to Top News

+<%= render partial: "partials/structure1", locals: {model_name: "article", + primary: "articles", + secondary: "liked_articles", + sidebar: nil} %> -<%= link_to "liked articles", liked_articles_url%> - - - - - \ - - - - -<% for article in @articles %> - - - - - - - - - - - <% if user_signed_in? %> - - <%else%> - - <% end -%> - - - - <% end %> -
TitleAuthorScoreLike?
<%= link_to article.title, article.url %><%= article.author %><%= article.score %> <%if !article.user_articles&.present?%> - <%= link_to 'like',like_path(id:article.article_id), method: :patch%> - <%else%> - <%= link_to 'unlike',unlike_path(id:article.article_id), method: :patch%> - <% end -%> please sign in to like articles - -
diff --git a/app/views/articles/show.html.erb b/app/views/articles/show.html.erb index ce9c2492..5d689170 100644 --- a/app/views/articles/show.html.erb +++ b/app/views/articles/show.html.erb @@ -1,8 +1,7 @@

<%= @article.title %>

-<%= link_to "top stories", articles_url%> -<%= link_to "liked articles", liked_articles_url%> -<%= link_to "read article", @article.url%> + +<%= link_to 'read the article', @article.url%> <% if user_signed_in? %> <% if !(@users_toggle.nil?) %> @@ -21,8 +20,9 @@

    <% if !@article.user_articles.empty? %> +

    Users who liked this article

    <% for user_article in @article.user_articles %> -
  • <%= user_article.user.first_name %>
  • +
  • <%= user_article.user.first_name + " " + user_article.user.last_name%>
  • <% end %> <%else%>
  • No Likes
  • diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 331a7ed0..dee81226 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -8,6 +8,7 @@ <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> + <%= render :partial => "partials/nav" %>

    <%= notice %>

    diff --git a/app/views/partials/_nav.html.erb b/app/views/partials/_nav.html.erb index 9d7bf24c..64c7f856 100644 --- a/app/views/partials/_nav.html.erb +++ b/app/views/partials/_nav.html.erb @@ -1,9 +1,20 @@ \ No newline at end of file diff --git a/app/views/partials/_nav_guest.html.erb b/app/views/partials/_nav_guest.html.erb deleted file mode 100644 index b759c491..00000000 --- a/app/views/partials/_nav_guest.html.erb +++ /dev/null @@ -1,7 +0,0 @@ - \ No newline at end of file From f145ff48f0dff85db7e349967a5645284d14d410 Mon Sep 17 00:00:00 2001 From: Allison Cao Date: Thu, 25 May 2023 14:34:19 -0400 Subject: [PATCH 6/7] added headers --- app/views/articles/_articles.html.erb | 3 ++- app/views/articles/_liked_articles.html.erb | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/views/articles/_articles.html.erb b/app/views/articles/_articles.html.erb index 76726e17..92f53448 100644 --- a/app/views/articles/_articles.html.erb +++ b/app/views/articles/_articles.html.erb @@ -2,11 +2,12 @@ - \ + + <% for article in @articles %> diff --git a/app/views/articles/_liked_articles.html.erb b/app/views/articles/_liked_articles.html.erb index 2f844ff4..8a489365 100644 --- a/app/views/articles/_liked_articles.html.erb +++ b/app/views/articles/_liked_articles.html.erb @@ -1,6 +1,8 @@ +

    Liked Articles

    +
    TitleTitle Author Score Like?
    - \ + From 6e2f96fef659047e8ec93222d31aa5fa7c9f0a4f Mon Sep 17 00:00:00 2001 From: Allison Cao Date: Wed, 31 May 2023 17:08:57 -0400 Subject: [PATCH 7/7] sign in bug fixed --- config/routes.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/routes.rb b/config/routes.rb index 213f72d1..f5a8f3f8 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -7,8 +7,10 @@ devise_for :users devise_scope :user do + # root to: "articles#index" get '/users/sign_out' => 'devise/sessions#destroy' post '/users/sign_up' => 'devise/registrations#create' + match '/sessions/user', to: 'devise/sessions#create', via: :post end # get '/articles', to: "articles#index"
    TitleTitle Author Score Likes