diff --git a/.gitignore b/.gitignore index 48fb168f..527ac329 100644 --- a/.gitignore +++ b/.gitignore @@ -13,5 +13,7 @@ !/log/.keep !/tmp/.keep + # Ignore Byebug command history file. .byebug_history +.env diff --git a/Gemfile b/Gemfile index c029c6da..169a2103 100644 --- a/Gemfile +++ b/Gemfile @@ -40,6 +40,9 @@ gem 'jbuilder', '~> 2.5' gem 'foundation-rails' gem 'autoprefixer-rails' +gem "omniauth" +gem "omniauth-github" + group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug', platform: :mri @@ -65,6 +68,8 @@ group :development do # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring gem 'spring' gem 'spring-watcher-listen', '~> 2.0.0' + gem 'dotenv-rails' + gem 'binding_of_caller' end # Windows does not include zoneinfo files, so bundle the tzinfo-data gem diff --git a/Gemfile.lock b/Gemfile.lock index f03db854..a8a75516 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -51,6 +51,8 @@ GEM erubi (>= 1.0.0) rack (>= 0.9.0) bindex (0.5.0) + binding_of_caller (0.8.0) + debug_inspector (>= 0.0.1) builder (3.2.3) byebug (10.0.2) coderay (1.1.2) @@ -63,9 +65,16 @@ GEM coffee-script-source (1.12.2) concurrent-ruby (1.0.5) crass (1.0.3) + debug_inspector (0.0.3) + dotenv (2.2.2) + dotenv-rails (2.2.2) + dotenv (= 2.2.2) + railties (>= 3.2, < 6.0) erubi (1.7.1) erubis (2.7.0) execjs (2.7.0) + faraday (0.12.2) + multipart-post (>= 1.2, < 3) ffi (1.9.23) foundation-rails (6.4.3.0) railties (>= 3.1.0) @@ -73,6 +82,7 @@ GEM sprockets-es6 (>= 0.9.0) globalid (0.4.1) activesupport (>= 4.2.0) + hashie (3.5.7) i18n (1.0.0) concurrent-ruby (~> 1.0) jbuilder (2.7.0) @@ -82,6 +92,7 @@ GEM rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) + jwt (1.5.6) listen (3.0.8) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) @@ -108,9 +119,26 @@ GEM minitest (~> 5.0) rails (>= 4.1) multi_json (1.13.1) + multi_xml (0.6.0) + multipart-post (2.0.0) nio4r (2.3.0) nokogiri (1.8.2) mini_portile2 (~> 2.3.0) + oauth2 (1.4.0) + faraday (>= 0.8, < 0.13) + jwt (~> 1.0) + multi_json (~> 1.3) + multi_xml (~> 0.5) + rack (>= 1.2, < 3) + omniauth (1.8.1) + hashie (>= 3.4.6, < 3.6.0) + rack (>= 1.6.2, < 3) + omniauth-github (1.3.0) + omniauth (~> 1.5) + omniauth-oauth2 (>= 1.4.0, < 2.0) + omniauth-oauth2 (1.5.0) + oauth2 (~> 1.1) + omniauth (~> 1.2) pg (0.21.0) pry (0.11.3) coderay (~> 1.1.0) @@ -197,8 +225,10 @@ PLATFORMS DEPENDENCIES autoprefixer-rails better_errors + binding_of_caller byebug coffee-rails (~> 4.2) + dotenv-rails foundation-rails jbuilder (~> 2.5) jquery-rails @@ -207,6 +237,8 @@ DEPENDENCIES minitest-reporters minitest-skip minitest-spec-rails + omniauth + omniauth-github pg (~> 0.18) pry-rails puma (~> 3.0) diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 5bce99e6..a1d1b038 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -3,28 +3,63 @@ def login_form end def login - username = params[:username] - if username and user = User.find_by(username: username) - session[:user_id] = user.id - flash[:status] = :success - flash[:result_text] = "Successfully logged in as existing user #{user.username}" - else - user = User.new(username: username) - if user.save - session[:user_id] = user.id - flash[:status] = :success - flash[:result_text] = "Successfully created new user #{user.username} with ID #{user.id}" + username = params[:username] + if username and user = User.find_by(username: username) + session[:user_id] = user.id + flash[:status] = :success + flash[:result_text] = "Successfully logged in as existing user #{user.username}" + else + user = User.new(username: username) + if user.save + session[:user_id] = user.id + flash[:status] = :success + flash[:result_text] = "Successfully created new user #{user.username} with ID #{user.id}" + else + flash.now[:status] = :failure + flash.now[:result_text] = "Could not log in" + flash.now[:messages] = user.errors.messages + render "login_form", status: :bad_request + return + end + end + redirect_to root_path + end + + + def create + auth_hash = request.env['omniauth.auth'] + + if auth_hash['uid'] + @user = User.find_by(uid: auth_hash[:uid], provider: 'github') + if @user.nil? + @user = User.new( + name: auth_hash['info']['name'], + email: auth_hash['info']['email'], + uid: auth_hash['uid'], + provider: auth_hash['provider']) + + if @user.save + session[:user_id] = @user.id + flash[:status] = :success + flash[:result_text] = "Successfully logged in" + redirect_to root_path + else + flash[:status] = :failure + flash[:result_text] = "Could not log in" + end else - flash.now[:status] = :failure - flash.now[:result_text] = "Could not log in" - flash.now[:messages] = user.errors.messages - render "login_form", status: :bad_request - return + session[:user_id] = @user.id + flash[:success] = "Logged in successfully" + redirect_to root_path end + else + flash[:error] = "Could not log in" + redirect_to root_path end - redirect_to root_path end + + def logout session[:user_id] = nil flash[:status] = :success diff --git a/app/controllers/works_controller.rb b/app/controllers/works_controller.rb index 2020bee4..6084055c 100644 --- a/app/controllers/works_controller.rb +++ b/app/controllers/works_controller.rb @@ -50,7 +50,7 @@ def update flash.now[:status] = :failure flash.now[:result_text] = "Could not update #{@media_category.singularize}" flash.now[:messages] = @work.errors.messages - render :edit, status: :not_found + render :edit, status: :bad_request end end diff --git a/app/models/user.rb b/app/models/user.rb index 4cac8fe0..9d4e8c09 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -2,5 +2,5 @@ class User < ApplicationRecord has_many :votes has_many :ranked_works, through: :votes, source: :work - validates :username, uniqueness: true, presence: true + # validates :username, uniqueness: true, presence: true end diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 82ca0fdc..dfd0a4f7 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -25,11 +25,13 @@ <%= link_to "View all users", users_path, class: "button" %>
- <% if @login_user %> - <%= link_to "Logged in as #{@login_user.username}", user_path(@login_user), class: "button" %> - <%= link_to "Log Out", logout_path, method: :post, class: "button" %> + <% if session[:user_id] %> + <% @user = User.find(session[:user_id]) %> + <%= link_to "Logged in as #{@user.name}", class: "button" %> + <%= link_to "Logout", logout_path, method: :post, class: "button" %> + <% else %> - <%= link_to "Log In", login_path, class: "button" %> + <%= link_to "Login", '/auth/github', class: "button" %> <% end %>
diff --git a/app/views/users/index.html.erb b/app/views/users/index.html.erb index 83570de1..72dfc690 100644 --- a/app/views/users/index.html.erb +++ b/app/views/users/index.html.erb @@ -10,7 +10,7 @@ <% @users.each do |user| %> - <%= link_to user.username, user_path(user) %> + <%= link_to user.name, user_path(user) %> <%= user.votes.count %> <%= render_date user.created_at %> diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index d6185ad7..496f4a0b 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -1,4 +1,4 @@ -

User Summary: <%= link_to @user.username, user_path(@user) %>

+

User Summary: <%= link_to @user.name, user_path(@user) %>

Joined site <%= render_date @user.created_at %>

Votes

diff --git a/app/views/works/show.html.erb b/app/views/works/show.html.erb index 13225855..08e4d8d5 100644 --- a/app/views/works/show.html.erb +++ b/app/views/works/show.html.erb @@ -23,7 +23,7 @@ <% @votes.each do |vote| %> - <%= link_to vote.user.username, user_path(vote.user) %> + <%= link_to vote.user.name, user_path(vote.user) %> <%= render_date vote.created_at %> <% end %> diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb new file mode 100644 index 00000000..fd441612 --- /dev/null +++ b/config/initializers/omniauth.rb @@ -0,0 +1,3 @@ +Rails.application.config.middleware.use OmniAuth::Builder do + provider :github, ENV["GITHUB_CLIENT_ID"], ENV["GITHUB_CLIENT_SECRET"], scope: "user:email" +end diff --git a/config/routes.rb b/config/routes.rb index a7e8af1d..b2839c13 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -9,4 +9,6 @@ post '/works/:id/upvote', to: 'works#upvote', as: 'upvote' resources :users, only: [:index, :show] + + get "/auth/:provider/callback", to: "sessions#create" end diff --git a/db/migrate/20180417190911_add_and_remove_columns_to_user_table.rb b/db/migrate/20180417190911_add_and_remove_columns_to_user_table.rb new file mode 100644 index 00000000..bd312fe3 --- /dev/null +++ b/db/migrate/20180417190911_add_and_remove_columns_to_user_table.rb @@ -0,0 +1,10 @@ +class AddAndRemoveColumnsToUserTable < ActiveRecord::Migration[5.0] + def change + add_column :users, :name, :string + add_column :users, :email, :string + add_column :users, :uid, :integer, null:false + add_column :users, :provider, :string, null:false + + remove_column :users, :username + end +end diff --git a/db/schema.rb b/db/schema.rb index 6bc8ba5c..1afbe25d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,15 +10,18 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20170407164321) do +ActiveRecord::Schema.define(version: 20180417190911) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" create_table "users", force: :cascade do |t| - t.string "username" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.string "name" + t.string "email" + t.integer "uid", null: false + t.string "provider", null: false end create_table "votes", force: :cascade do |t| diff --git a/test/controllers/users_controller_test.rb b/test/controllers/users_controller_test.rb index d2c5cfbb..6f28a576 100644 --- a/test/controllers/users_controller_test.rb +++ b/test/controllers/users_controller_test.rb @@ -1,5 +1,45 @@ require 'test_helper' describe UsersController do + describe 'index' do + it "succeeds when there are users" do + # Assumptions + User.count.must_be :>,0 + # Act + get users_path + + # Assert + must_respond_with :success + end + + + it "succeeds when there are no users" do + skip + # Assumptions + # User.destroy_all + # + # # Act + # get user_path + # + # # Assert + # must_respond_with :success + end + end + + describe 'show' do + it "succeeds for an existing user" do + get user_path(User.first) + + must_respond_with :success + end + + it "renders 404 not_found for a nonexistent user" do + user_id = User.last.id + 1 + + get user_path(user_id) + + must_respond_with :not_found + end + end end diff --git a/test/controllers/works_controller_test.rb b/test/controllers/works_controller_test.rb index 0945ca47..5026d93b 100644 --- a/test/controllers/works_controller_test.rb +++ b/test/controllers/works_controller_test.rb @@ -5,15 +5,44 @@ it "succeeds with all media types" do # Precondition: there is at least one media of each category + # Assumptions + Work.best_albums.count.must_be :>, 0 + Work.best_books.count.must_be :>, 0 + Work.best_movies.count.must_be :>, 0 + + # Act + get root_path + + # Assert + must_respond_with :success end it "succeeds with one media type absent" do # Precondition: there is at least one media in two of the categories + # Assumptions + Work.best_movies.destroy_all + Work.best_albums.count.must_be :>, 0 + Work.best_books.count.must_be :>, 0 + + # Act + get root_path + + # Assert + must_respond_with :success end it "succeeds with no media" do + # Assumptions + Work.best_albums.destroy_all + Work.best_books.destroy_all + Work.best_movies.destroy_all + + # Act + get root_path + # Assert + must_respond_with :success end end @@ -23,75 +52,220 @@ describe "index" do it "succeeds when there are works" do + # Assumptions + Work.count.must_be :>, 0 + + #Act + get works_path + + #Assert + must_respond_with :success + end it "succeeds when there are no works" do + # Assumptions + Work.destroy_all + + #Act + get works_path + + # Assert + must_respond_with :success end end describe "new" do it "succeeds" do + # Act + get new_work_path + # Assert + must_respond_with :success end end describe "create" do it "creates a work with valid data for a real category" do + # Arrange + work_data = { + title: 'new movie', + category: 'movie' + + } + previous_count = Work.count + + # Assumptions + Work.new(work_data).must_be :valid? + + # Act + post works_path, params: { work: work_data } + + # Assert + must_respond_with :redirect + # must_redirect_to works_path + + Work.count.must_equal previous_count + 1 + Work.last.title.must_equal work_data[:title] + end it "renders bad_request and does not update the DB for bogus data" do + # Arrange + work_data = { + category: 'movie' + } + previous_count = Work.count + # Assumptions + Work.new(work_data).wont_be :valid? + + # Act + post works_path, params: { work: work_data } + + # Assert + must_respond_with :bad_request + Work.count.must_equal previous_count end it "renders 400 bad_request for bogus categories" do + # Arrange + work_data = { + category: 'film' + } + previous_count = Work.count + + # Assumptions + Work.new(work_data).wont_be :valid? + # Act + post works_path, params: { work: work_data } + + # Assert + must_respond_with :bad_request + Work.count.must_equal previous_count end end describe "show" do it "succeeds for an extant work ID" do + # Act + get work_path(Work.first) + # Assert + must_respond_with :success end it "renders 404 not_found for a bogus work ID" do + # Arrange + work_id = Work.last.id + 1 + + # Act + get work_path(work_id) + # Assert + must_respond_with :not_found end end describe "edit" do it "succeeds for an extant work ID" do + # Act + get edit_work_path(Work.first) + # Assert + must_respond_with :success end it "renders 404 not_found for a bogus work ID" do + # Arrange + work_id = Work.last.id + 1 + # Act + get edit_work_path(work_id) + + # Assert + must_respond_with :not_found end end describe "update" do it "succeeds for valid data and an extant work ID" do + # Arrange + work = Work.first + work_data = work.attributes + work_data[:title] = "new test title" + + # Assumptions + work.assign_attributes(work_data) + work.must_be :valid? + + # Act + patch work_path(work), params: { work: work_data } + + # Assert + must_redirect_to work_path(work) + must_respond_with :redirect + + work.reload + work.title.must_equal work_data[:title] end it "renders bad_request for bogus data" do + # Arrange + work = Work.first + work_data = work.attributes + work_data[:title] = nil + + # Assumptions + work.assign_attributes(work_data) + work.wont_be :valid? + + # Act + patch work_path(work), params: { work: work_data } + + # Assert + # must_redirect_to work_path(work) + must_respond_with :bad_request + + work.reload end it "renders 404 not_found for a bogus work ID" do + work_id = Work.last.id + 1 + + patch work_path(work_id) + must_respond_with :not_found end end describe "destroy" do it "succeeds for an extant work ID" do + work_id = Work.last.id + previous_count = Work.count + delete work_path(work_id) + + must_respond_with :redirect + Work.count.must_equal previous_count - 1 + + Work.find_by(id: work_id).must_be_nil end it "renders 404 not_found and does not update the DB for a bogus work ID" do + work_id = Work.last.id + 1 + previous_count = Work.count + + delete work_path(work_id) + must_respond_with :not_found + Work.count.must_equal previous_count end end @@ -99,18 +273,60 @@ it "redirects to the work page if no user is logged in" do + id = Work.first.id + + # Act + post upvote_path(id) + + # Assert + must_respond_with :redirect + must_redirect_to work_path end it "redirects to the work page after the user has logged out" do + # Arrange + user = User.first + post login_url + work = Work.first + + # Act + + post logout_path(user.id) + post upvote_path(work.id) + + # Assert + must_respond_with :redirect + must_redirect_to work_path end it "succeeds for a logged-in user and a fresh user-vote pair" do + # Arrange + user = User.first + post login_url + work = Work.first + # Act + post upvote_path(work.id) + + # Assert + must_respond_with :redirect + must_redirect_to work_path(work.id) end it "redirects to the work page if the user has already voted for that work" do - + # Arrange + user = users(:dan) + post login_url + work = works(:album) + + # Act + post upvote_path(work.id) + post upvote_path(work.id) + + # Assert + must_respond_with :redirect + must_redirect_to work_path(work.id) end end end diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml index e2968d78..a3ffab47 100644 --- a/test/fixtures/users.yml +++ b/test/fixtures/users.yml @@ -1,7 +1,11 @@ # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html dan: - username: dan + name: dan + uid: 1 + provider: github kari: - username: kari + name: kari + uid: 2 + provider: github