diff --git a/Gemfile b/Gemfile index 5a8ffc43..3d540a61 100644 --- a/Gemfile +++ b/Gemfile @@ -2,21 +2,28 @@ source 'https://rubygems.org' ruby File.read('.ruby-version').chomp -gem 'byebug', platforms: [:mri, :mingw, :x64_mingw], group: [:development, :test] -gem 'capybara', group: [:development, :test] gem 'coffee-rails' gem 'devise' gem 'jbuilder' -gem 'listen', group: :development gem 'pg' gem 'pry-rails' gem 'puma' gem 'rails', '~> 7.0.3' -gem 'rspec-rails' gem 'sass-rails' -gem 'selenium-webdriver', group: [:development, :test] -gem 'spring', group: :development gem 'turbolinks' gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] gem 'uglifier' -gem 'web-console', group: :development +gem 'faraday' + +group :development, :test do + gem 'rspec-rails' + gem 'selenium-webdriver' + gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] + gem 'capybara' +end + +group :development do + gem 'spring' + gem 'web-console' + gem 'listen' +end diff --git a/Gemfile.lock b/Gemfile.lock index 14ec6457..730480e2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -68,7 +68,9 @@ GEM tzinfo (~> 2.0) addressable (2.8.1) public_suffix (>= 2.0.2, < 6.0) + base64 (0.2.0) bcrypt (3.1.18) + bigdecimal (3.1.8) bindex (0.8.1) builder (3.2.4) byebug (11.1.3) @@ -102,17 +104,77 @@ GEM digest (3.1.0) erubi (1.11.0) execjs (2.8.1) + faraday (2.10.0) + faraday-net_http (>= 2.0, < 3.2) + logger + faraday-net_http (3.1.0) + net-http + faraday-retry (2.2.1) + faraday (~> 2.0) ffi (1.15.5) + gapic-common (0.22.0) + faraday (>= 1.9, < 3.a) + faraday-retry (>= 1.0, < 3.a) + google-protobuf (>= 3.25, < 5.a) + googleapis-common-protos (~> 1.6) + googleapis-common-protos-types (~> 1.15) + googleauth (~> 1.11) + grpc (~> 1.65) globalid (1.0.0) activesupport (>= 5.0) + google-cloud-core (1.7.0) + google-cloud-env (>= 1.0, < 3.a) + google-cloud-errors (~> 1.0) + google-cloud-env (2.1.1) + faraday (>= 1.0, < 3.a) + google-cloud-errors (1.4.0) + google-cloud-firestore (2.16.0) + bigdecimal (~> 3.0) + concurrent-ruby (~> 1.0) + google-cloud-core (~> 1.5) + google-cloud-firestore-v1 (>= 0.10, < 2.a) + rbtree (~> 0.4.2) + google-cloud-firestore-v1 (1.0.0) + gapic-common (>= 0.21.1, < 2.a) + google-cloud-errors (~> 1.0) + google-cloud-location (>= 0.7, < 2.a) + google-cloud-location (0.8.0) + gapic-common (>= 0.21.1, < 2.a) + google-cloud-errors (~> 1.0) + google-protobuf (4.27.2) + bigdecimal + rake (>= 13) + googleapis-common-protos (1.6.0) + google-protobuf (>= 3.18, < 5.a) + googleapis-common-protos-types (~> 1.7) + grpc (~> 1.41) + googleapis-common-protos-types (1.15.0) + google-protobuf (>= 3.18, < 5.a) + googleauth (1.11.0) + faraday (>= 1.0, < 3.a) + google-cloud-env (~> 2.1) + jwt (>= 1.4, < 3.0) + multi_json (~> 1.11) + os (>= 0.9, < 2.0) + signet (>= 0.16, < 2.a) + grpc (1.65.1) + google-protobuf (>= 3.25, < 5.0) + googleapis-common-protos-types (~> 1.0) i18n (1.12.0) concurrent-ruby (~> 1.0) jbuilder (2.11.5) actionview (>= 5.0.0) activesupport (>= 5.0.0) + jwt (2.8.2) + base64 + launchy (2.5.2) + addressable (~> 2.8) + letter_opener (1.10.0) + launchy (>= 2.2, < 4) listen (3.7.1) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) + logger (1.6.0) loofah (2.19.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) @@ -124,6 +186,9 @@ GEM mini_mime (1.1.2) mini_portile2 (2.8.0) minitest (5.16.3) + multi_json (1.15.0) + net-http (0.4.1) + uri net-imap (0.2.3) digest net-protocol @@ -143,6 +208,7 @@ GEM mini_portile2 (~> 2.8.0) racc (~> 1.4) orm_adapter (0.5.0) + os (1.1.4) pg (1.4.3) pry (0.14.1) coderay (~> 1.1) @@ -186,6 +252,7 @@ GEM rb-fsevent (0.11.2) rb-inotify (0.10.1) ffi (~> 1.0) + rbtree (0.4.6) regexp_parser (2.5.0) responders (3.0.1) actionpack (>= 5.0) @@ -224,6 +291,11 @@ GEM rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) + signet (0.19.0) + addressable (~> 2.8) + faraday (>= 0.17.5, < 3.a) + jwt (>= 1.5, < 3.0) + multi_json (~> 1.10) spring (4.1.0) sprockets (4.1.1) concurrent-ruby (~> 1.0) @@ -243,6 +315,7 @@ GEM concurrent-ruby (~> 1.0) uglifier (4.2.0) execjs (>= 0.3.0, < 3) + uri (0.13.0) warden (1.2.9) rack (>= 2.0.9) web-console (4.2.0) @@ -266,7 +339,10 @@ DEPENDENCIES capybara coffee-rails devise + faraday + google-cloud-firestore jbuilder + letter_opener listen pg pry-rails diff --git a/app/controllers/news_items_controller.rb b/app/controllers/news_items_controller.rb new file mode 100644 index 00000000..d6e05909 --- /dev/null +++ b/app/controllers/news_items_controller.rb @@ -0,0 +1,13 @@ +class NewsItemsController < ApplicationController + def index + @news_items = NewsItem.all + end + + def create + @news_item = NewsItem.find_or_initialize_by(hacker_news_id: params[:hacker_news_id], title: params[:title], url: params[:url], item_type: params[:type]) + @news_item.favorites.build(user_id: current_user.id) + if @news_item.save + redirect_to news_items_url + end + end +end diff --git a/app/controllers/pages_controller.rb b/app/controllers/pages_controller.rb index ce3bf586..a4e30d41 100644 --- a/app/controllers/pages_controller.rb +++ b/app/controllers/pages_controller.rb @@ -1,2 +1,8 @@ class PagesController < ApplicationController + + def home + news_service = ApiClient.new + + @hacker_news_items = news_service.top_stories + end end diff --git a/app/models/favorite.rb b/app/models/favorite.rb new file mode 100644 index 00000000..a64c3f59 --- /dev/null +++ b/app/models/favorite.rb @@ -0,0 +1,4 @@ +class Favorite < ApplicationRecord + belongs_to :user + belongs_to :news_item +end diff --git a/app/models/news_item.rb b/app/models/news_item.rb new file mode 100644 index 00000000..2b79a93b --- /dev/null +++ b/app/models/news_item.rb @@ -0,0 +1,5 @@ +class NewsItem < ApplicationRecord + + has_many :favorites + has_many :users, through: :favorites +end diff --git a/app/models/user.rb b/app/models/user.rb index b2091f9a..87004345 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -3,4 +3,8 @@ class User < ApplicationRecord # :confirmable, :lockable, :timeoutable and :omniauthable devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable + + + has_many :favorites + has_many :news_items, through: :favorites end diff --git a/app/services/api_client.rb b/app/services/api_client.rb new file mode 100644 index 00000000..f4b58848 --- /dev/null +++ b/app/services/api_client.rb @@ -0,0 +1,33 @@ +class ApiClient + BASE_URL = "https://hacker-news.firebaseio.com" + + def initialize + @connection = Faraday.new(url: BASE_URL) do |faraday| + faraday.response :logger + faraday.adapter Faraday.default_adapter + faraday.headers[''] = '' + end + end + + def top_item_ids + response = @connection.get '/v0/topstories.json' + JSON.parse response.body + end + + def top_stories(limit=30) + top_item_ids[0..limit].map{|item_id| get_story(item_id)}.compact + end + + def get_story(story_id) + response = @connection.get "/v0/item/#{story_id}.json" + Struct.new("Item", :hacker_news_id, :url, :title, :type, :liked) + + parsed_response = JSON.parse response.body + + if parsed_response + Struct::Item.new(parsed_response['id'], parsed_response['url'], parsed_response['title'], parsed_response['type'], false) + end + + end + +end \ No newline at end of file diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 331a7ed0..2f659416 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -14,6 +14,13 @@

<%= alert %>

+ <%= link_to "Home", root_path %> + <% if user_signed_in? -%> + <%= link_to "Team Likes", news_items_path %> + <%= link_to "sign out", destroy_user_session_path, :method => :delete %> + <% else -%> + <%= link_to "sign in", "/users/sign_in" %> + <% end -%> <%= yield %> diff --git a/app/views/news_items/index.html.erb b/app/views/news_items/index.html.erb new file mode 100644 index 00000000..dfec7554 --- /dev/null +++ b/app/views/news_items/index.html.erb @@ -0,0 +1,15 @@ +

Team Favorites

+ \ No newline at end of file diff --git a/app/views/pages/home.html.erb b/app/views/pages/home.html.erb index 8bfd8294..78162153 100644 --- a/app/views/pages/home.html.erb +++ b/app/views/pages/home.html.erb @@ -1 +1,14 @@

Welcome to Top News

+ \ No newline at end of file diff --git a/config/environments/test.rb b/config/environments/test.rb index 8e5cbde5..cbefa9f4 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 @@ -37,6 +37,8 @@ # Print deprecation notices to the stderr. config.active_support.deprecation = :stderr + config.active_record.legacy_connection_handling = false + # Raises error for missing translations # config.action_view.raise_on_missing_translations = true end diff --git a/config/routes.rb b/config/routes.rb index c12ef082..0c3f4bcf 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,5 @@ Rails.application.routes.draw do + resources :news_items, only: [:index, :create] devise_for :users root to: 'pages#home' end diff --git a/db/migrate/20240724092828_create_news_items.rb b/db/migrate/20240724092828_create_news_items.rb new file mode 100644 index 00000000..dfb33fe0 --- /dev/null +++ b/db/migrate/20240724092828_create_news_items.rb @@ -0,0 +1,14 @@ +class CreateNewsItems < ActiveRecord::Migration[7.0] + def change + create_table :news_items do |t| + t.string :title + t.string :item_type + t.string :url + t.integer :hacker_news_id + + t.timestamps + + t.index :hacker_news_id + end + end +end diff --git a/db/migrate/20240724095033_create_favorites.rb b/db/migrate/20240724095033_create_favorites.rb new file mode 100644 index 00000000..777cd463 --- /dev/null +++ b/db/migrate/20240724095033_create_favorites.rb @@ -0,0 +1,10 @@ +class CreateFavorites < ActiveRecord::Migration[7.0] + def change + create_table :favorites do |t| + t.integer :user_id + t.integer :news_item_id + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index acc34f3b..dfde6f09 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,10 +10,27 @@ # # 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_07_24_095033) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" + create_table "favorites", force: :cascade do |t| + t.integer "user_id" + t.integer "news_item_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "news_items", force: :cascade do |t| + t.string "title" + t.string "item_type" + t.string "url" + t.integer "hacker_news_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["hacker_news_id"], name: "index_news_items_on_hacker_news_id" + end + create_table "users", force: :cascade do |t| t.string "first_name" t.string "last_name" diff --git a/spec/models/news_item_spec.rb b/spec/models/news_item_spec.rb new file mode 100644 index 00000000..587a4f2a --- /dev/null +++ b/spec/models/news_item_spec.rb @@ -0,0 +1,14 @@ +require 'rails_helper' + +RSpec.describe NewsItem, type: :model do + + context "creating a new news_item" do + let(:attrs) do + { hacker_news_id: 322222, url: 'url_for_news', title: 'TestTitle', item_type: 'article' } + end + + it "should have hacker_news_id, url, title, and item_type" do + expect { NewsItem.create(attrs) }.to change{ NewsItem.count }.by(1) + end + end +end