diff --git a/Gemfile b/Gemfile index fd2e2b45..01654611 100644 --- a/Gemfile +++ b/Gemfile @@ -21,3 +21,5 @@ gem 'turbolinks' gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] gem 'uglifier' gem 'web-console', group: :development + +gem "httparty", "~> 0.22.0" diff --git a/Gemfile.lock b/Gemfile.lock index 7d7a3577..695ba413 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -70,6 +70,7 @@ GEM public_suffix (>= 2.0.2, < 7.0) base64 (0.2.0) bcrypt (3.1.20) + bigdecimal (3.1.8) bindex (0.8.1) builder (3.3.0) byebug (11.1.3) @@ -92,6 +93,7 @@ GEM coffee-script-source (1.12.2) concurrent-ruby (1.3.4) crass (1.0.6) + csv (3.3.0) date (3.3.4) devise (4.9.4) bcrypt (~> 3.0) @@ -110,6 +112,12 @@ GEM ffi (1.17.0) globalid (1.2.1) activesupport (>= 6.1) + httparty (0.22.0) + csv + mini_mime (>= 1.0.0) + multi_xml (>= 0.5.2) + httpparty (0.2.0) + httparty (> 0) i18n (1.14.5) concurrent-ruby (~> 1.0) jbuilder (2.12.0) @@ -133,6 +141,8 @@ GEM mini_mime (1.1.5) mini_portile2 (2.8.7) minitest (5.25.1) + multi_xml (0.7.1) + bigdecimal (~> 3.1) net-imap (0.4.14) date net-protocol @@ -275,6 +285,8 @@ DEPENDENCIES coffee-rails devise factory_bot_rails + httparty (~> 0.22.0) + httpparty (~> 0.2.0) jbuilder listen pg diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 1c07694e..4e5617f5 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,3 +1,4 @@ class ApplicationController < ActionController::Base protect_from_forgery with: :exception + before_action :authenticate_user! end diff --git a/app/controllers/pages_controller.rb b/app/controllers/pages_controller.rb index ce3bf586..96626a2f 100644 --- a/app/controllers/pages_controller.rb +++ b/app/controllers/pages_controller.rb @@ -1,2 +1,12 @@ class PagesController < ApplicationController + + def home + stories_flagged = Flag.stories_flagged() + @stories = helpers.fetch_story_details(stories_flagged.keys) + @stories.each do |story| + story_id = story['id'] + story['flags'] = stories_flagged[story_id].map(&:user).map(&:email).join(', ') if stories_flagged[story_id] + end + end + end diff --git a/app/controllers/stories_controller.rb b/app/controllers/stories_controller.rb new file mode 100644 index 00000000..a5590462 --- /dev/null +++ b/app/controllers/stories_controller.rb @@ -0,0 +1,29 @@ +class StoriesController < ApplicationController + + def index + top_story_ids = helpers.fetch_top_stories.first(10) + @stories = helpers.fetch_story_details(top_story_ids) + @flagged_story_ids = current_user.flags.pluck(:story_id) + + @stories.each do |story| + story_id = story['id'] + story['flagged'] = @flagged_story_ids.member?(story_id) + end + end + + def show + story_id = params[:id] + + flag = current_user.flags.find_by(story_id: story_id) + if flag + notice = 'Story was successfully unflagged.' + flag.destroy! + else + notice = 'Story was successfully flagged.' + Flag.create!(story_id: story_id, user_id: current_user.id) + end + + redirect_to(stories_path, notice: notice) + end + +end diff --git a/app/controllers/users/sessions_controller.rb b/app/controllers/users/sessions_controller.rb new file mode 100644 index 00000000..d2daa38c --- /dev/null +++ b/app/controllers/users/sessions_controller.rb @@ -0,0 +1,2 @@ +class Users::SessionsController < Devise::SessionsController +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index de6be794..080f279f 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,2 +1,17 @@ module ApplicationHelper + @@hacker_base= 'https://hacker-news.firebaseio.com/v0/' + + def fetch_top_stories + response = HTTParty.get("#{@@hacker_base}topstories.json") + JSON.parse(response.body) + end + + def fetch_story_details(story_ids) + details = story_ids.map do |id| + response = HTTParty.get("#{@@hacker_base}item/#{id}.json") + JSON.parse(response.body) + end + + details.compact + end end diff --git a/app/models/flag.rb b/app/models/flag.rb new file mode 100644 index 00000000..05d2551c --- /dev/null +++ b/app/models/flag.rb @@ -0,0 +1,12 @@ +class Flag < ApplicationRecord + belongs_to :user + + validates :story_id, presence: true + validates :user_id, presence: true + validates :story_id, uniqueness: { scope: :user_id, message: "has already been flagged by this user" } + + def Flag.stories_flagged() + flags = Flag.includes(:user).group_by(&:story_id) + return flags + end +end diff --git a/app/models/user.rb b/app/models/user.rb index b2091f9a..68036eff 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -3,4 +3,6 @@ class User < ApplicationRecord # :confirmable, :lockable, :timeoutable and :omniauthable devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable + + has_many :flags end diff --git a/app/views/application/_story.html.erb b/app/views/application/_story.html.erb new file mode 100644 index 00000000..27645666 --- /dev/null +++ b/app/views/application/_story.html.erb @@ -0,0 +1,24 @@ + <% @stories.each do |story| %> + +
+

+ <%= link_to story['title'], story['url'], target: '_blank' %> +

+ +

+ + Score: <%= story['score'] %> | + By: <%= story['by'] %> | + <%= pluralize(story['descendants'], 'comment') %> | + <%= link_to "Hacker News Discussion", "https://news.ycombinator.com/item?id=#{story['id']}", target: '_blank' %> +

+ <% if story['flags'].present? %> +

+ Flagged by: <%= story['flags'] %> +

+ <% end %> +

+ <%= link_to story['flagged'] ? 'Unflag!' :'Flag!' , story_path(story['id']) %> +

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

Log in

+ +<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %> +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email" %> +
+ +
+ <%= f.label :password %>
+ <%= f.password_field :password, autocomplete: "current-password" %> +
+ +
+ <%= f.submit "Log in" %> +
+<% end %> + +<%= 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) + %> +

+ +
+<% end %> diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 331a7ed0..934a7531 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -8,6 +8,9 @@ <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> + <% if user_signed_in? %> + <%= button_to 'Sign out', destroy_user_session_path, method: :delete %> + <% end %>

<%= notice %>

diff --git a/app/views/pages/home.html.erb b/app/views/pages/home.html.erb index 8bfd8294..2f273d87 100644 --- a/app/views/pages/home.html.erb +++ b/app/views/pages/home.html.erb @@ -1 +1,4 @@ -

Welcome to Top News

+

User Flagged Stories

+<%= link_to 'Current Top Stories', stories_path %> +<%= render 'story' %> + diff --git a/app/views/stories/index.html.erb b/app/views/stories/index.html.erb new file mode 100644 index 00000000..71f4470d --- /dev/null +++ b/app/views/stories/index.html.erb @@ -0,0 +1,4 @@ +

Top Hacker News Stories

+<%= link_to 'User Flagged Stories', '/' %> + +<%= render 'story' %> \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index c12ef082..8ea039ed 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,6 @@ Rails.application.routes.draw do + resources :stories, only: [:index, :show] + devise_for :users root to: 'pages#home' end diff --git a/db/migrate/20240830133222_create_flags.rb b/db/migrate/20240830133222_create_flags.rb new file mode 100644 index 00000000..812e4a3f --- /dev/null +++ b/db/migrate/20240830133222_create_flags.rb @@ -0,0 +1,10 @@ +class CreateFlags < ActiveRecord::Migration[7.0] + def change + create_table :flags do |t| + t.integer :story_id + t.integer :user_id + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index acc34f3b..d734f838 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,10 +10,17 @@ # # 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: 2024_08_30_133222) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" + create_table "flags", force: :cascade do |t| + t.integer "story_id" + t.integer "user_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "users", force: :cascade do |t| t.string "first_name" t.string "last_name"