<%= link_to interesting_story.story.title, interesting_story.story.url %>
+by <%= interesting_story.story.by %> | <%= interesting_story.story.score %> points
+Marked by: <%= interesting_story.user.email %>
+diff --git a/.gitignore b/.gitignore index 82701fed..0c05d48b 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,6 @@ /yarn-error.log .byebug_history +.idea/* +.DS_Store + diff --git a/Gemfile b/Gemfile index 5a8ffc43..038699f2 100644 --- a/Gemfile +++ b/Gemfile @@ -2,21 +2,37 @@ 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 'rails', '~> 7.0.3' 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 'coffee-rails' gem 'uglifier' -gem 'web-console', group: :development +gem 'turbolinks' +gem 'jbuilder' +gem 'devise' + +group :development, :test do + gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] + gem 'capybara' + gem 'selenium-webdriver' + gem 'webdrivers' + gem 'rspec-rails' + gem 'database_cleaner-active_record' + gem 'factory_bot_rails' + gem 'webmock' +end + +group :development do + gem 'listen' + gem 'spring' + gem 'web-console' + gem 'pry-rails' +end + +group :test do + gem 'simplecov', require: false # Optional, for test coverage + gem 'shoulda-matchers' # Optional, for simplified model specs +end + +gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index 14ec6457..9d3d60d2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -69,6 +69,7 @@ GEM addressable (2.8.1) public_suffix (>= 2.0.2, < 6.0) bcrypt (3.1.18) + bigdecimal (3.1.8) bindex (0.8.1) builder (3.2.4) byebug (11.1.3) @@ -91,7 +92,14 @@ GEM execjs coffee-script-source (1.12.2) concurrent-ruby (1.1.10) + crack (1.0.0) + bigdecimal + rexml crass (1.0.6) + database_cleaner-active_record (2.2.0) + activerecord (>= 5.a) + database_cleaner-core (~> 2.0.0) + database_cleaner-core (2.0.1) devise (4.8.1) bcrypt (~> 3.0) orm_adapter (~> 0.1) @@ -100,11 +108,18 @@ GEM warden (~> 1.2.3) diff-lcs (1.5.0) digest (3.1.0) + docile (1.4.0) erubi (1.11.0) execjs (2.8.1) + factory_bot (6.4.6) + activesupport (>= 5.0.0) + factory_bot_rails (6.4.3) + factory_bot (~> 6.4) + railties (>= 5.0.0) ffi (1.15.5) globalid (1.0.0) activesupport (>= 5.0) + hashdiff (1.1.0) i18n (1.12.0) concurrent-ruby (~> 1.0) jbuilder (2.11.5) @@ -224,6 +239,14 @@ GEM rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) + shoulda-matchers (6.2.0) + activesupport (>= 5.2.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.0) sprockets (4.1.1) concurrent-ruby (~> 1.0) @@ -250,6 +273,14 @@ GEM activemodel (>= 6.0.0) bindex (>= 0.4.0) railties (>= 6.0.0) + webdrivers (5.3.1) + nokogiri (~> 1.6) + rubyzip (>= 1.3.0) + selenium-webdriver (~> 4.0, < 4.11) + webmock (3.23.1) + addressable (>= 2.8.0) + crack (>= 0.3.2) + hashdiff (>= 0.4.0, < 2.0.0) websocket (1.2.9) websocket-driver (0.7.5) websocket-extensions (>= 0.1.0) @@ -265,7 +296,9 @@ DEPENDENCIES byebug capybara coffee-rails + database_cleaner-active_record devise + factory_bot_rails jbuilder listen pg @@ -275,11 +308,15 @@ DEPENDENCIES rspec-rails sass-rails selenium-webdriver + shoulda-matchers + simplecov spring turbolinks tzinfo-data uglifier web-console + webdrivers + webmock RUBY VERSION ruby 3.1.2p20 diff --git a/app/controllers/interesting_stories_controller.rb b/app/controllers/interesting_stories_controller.rb new file mode 100644 index 00000000..59bbcdc6 --- /dev/null +++ b/app/controllers/interesting_stories_controller.rb @@ -0,0 +1,7 @@ +class InterestingStoriesController < ApplicationController + before_action :authenticate_user! + + def index + @interesting_stories = InterestingStory.includes(:story, :user).all + end +end \ No newline at end of file diff --git a/app/controllers/stories_controller.rb b/app/controllers/stories_controller.rb new file mode 100644 index 00000000..1b6877f2 --- /dev/null +++ b/app/controllers/stories_controller.rb @@ -0,0 +1,32 @@ +# app/controllers/stories_controller.rb +class StoriesController < ApplicationController + before_action :authenticate_user! + + def index + @stories = Story.fetch_top_stories.take(10).map { |story_id| Story.fetch_story_details(story_id) } + end + + def mark_interesting + story_details = Story.fetch_story_details(params[:id].to_i) + @story = Story.find_or_initialize_by(story_id: story_details["id"]) + @story.update(story_params(story_details)) + + InterestingStory.create(user: current_user, story: @story) + + redirect_to interesting_stories_path, notice: 'Story marked as interesting.' + end + + private + + def story_params(details) + { + title: details["title"], + url: details["url"], + by: details["by"], + score: details["score"], + time: Time.at(details["time"]), + descendants: details["descendants"], + story_type: details["type"] + } + end +end \ No newline at end of file diff --git a/app/models/interesting_story.rb b/app/models/interesting_story.rb new file mode 100644 index 00000000..c8267a9f --- /dev/null +++ b/app/models/interesting_story.rb @@ -0,0 +1,6 @@ +# app/models/interesting_story.rb +class InterestingStory < ApplicationRecord + belongs_to :user + belongs_to :story + validates :user_id, uniqueness: { scope: :story_id, message: "has already marked this story as interesting" } +end \ No newline at end of file diff --git a/app/models/story.rb b/app/models/story.rb new file mode 100644 index 00000000..885bbb90 --- /dev/null +++ b/app/models/story.rb @@ -0,0 +1,20 @@ +# app/models/story.rb +require 'net/http' +require 'json' + +class Story < ApplicationRecord + has_many :interesting_stories + has_many :users, through: :interesting_stories + + def self.fetch_top_stories + url = URI.parse('https://hacker-news.firebaseio.com/v0/topstories.json') + response = Net::HTTP.get_response(url) + JSON.parse(response.body) + end + + def self.fetch_story_details(story_id) + url = URI.parse("https://hacker-news.firebaseio.com/v0/item/#{story_id}.json") + response = Net::HTTP.get_response(url) + JSON.parse(response.body) + end +end \ No newline at end of file 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 @@ +
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 @@ +Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { 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..d655b66f --- /dev/null +++ b/app/views/devise/registrations/new.html.erb @@ -0,0 +1,29 @@ +- <%= notice %> -
-- <%= alert %> -
- <%= yield %> - - + + +<%= notice %>
+<%= alert %>
+<%= yield %> + +