diff --git a/.idea/simple_app.iml b/.idea/simple_app.iml index aa7dd65..c5b220e 100644 --- a/.idea/simple_app.iml +++ b/.idea/simple_app.iml @@ -49,6 +49,7 @@ + @@ -62,13 +63,14 @@ + - + @@ -124,7 +126,7 @@ - + @@ -152,6 +154,7 @@ + diff --git a/Gemfile b/Gemfile index 8811d77..a4a99c0 100644 --- a/Gemfile +++ b/Gemfile @@ -16,10 +16,12 @@ gem "turbo-rails" gem "stimulus-rails" # Build JSON APIs with ease [https://github.com/rails/jbuilder] gem "jbuilder" -gem 'ostruct' -gem 'bcrypt', '~> 3.1.7' -gem 'pg' - +gem "ostruct" +gem "bcrypt", "~> 3.1.7" +gem "pg" +gem "faker" +gem "will_paginate", "3.3.1" +gem "bootstrap-will_paginate", "1.0.0" gem "sassc-rails" gem "rails-controller-testing" gem "bootstrap-sass", "~> 3.4.1" @@ -63,7 +65,6 @@ group :test do gem "guard-minitest" end - group :development do # Use console on exceptions pages [https://github.com/rails/web-console] gem "web-console" diff --git a/Gemfile.lock b/Gemfile.lock index a83b8ef..3ee2fdb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -86,6 +86,8 @@ GEM bootstrap-sass (3.4.1) autoprefixer-rails (>= 5.2.1) sassc (>= 2.0.0) + bootstrap-will_paginate (1.0.0) + will_paginate brakeman (6.2.1) racc builder (3.3.0) @@ -109,6 +111,8 @@ GEM drb (2.2.1) erubi (1.13.0) execjs (2.9.1) + faker (3.4.2) + i18n (>= 1.8.11, < 2) ffi (1.17.0-aarch64-linux-gnu) ffi (1.17.0-aarch64-linux-musl) ffi (1.17.0-arm-linux-gnu) @@ -135,7 +139,7 @@ GEM guard-minitest (2.4.6) guard-compat (~> 1.2) minitest (>= 3.0) - i18n (1.14.5) + i18n (1.14.6) concurrent-ruby (~> 1.0) importmap-rails (2.0.1) actionpack (>= 6.0.0) @@ -282,7 +286,7 @@ GEM rubocop-minitest (0.36.0) rubocop (>= 1.61, < 2.0) rubocop-ast (>= 1.31.1, < 2.0) - rubocop-performance (1.21.1) + rubocop-performance (1.22.1) rubocop (>= 1.48.1, < 2.0) rubocop-ast (>= 1.31.1, < 2.0) rubocop-rails (2.26.1) @@ -356,6 +360,7 @@ GEM websocket-driver (0.7.6) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) + will_paginate (3.3.1) xpath (3.2.0) nokogiri (~> 1.8) zeitwerk (2.6.18) @@ -379,9 +384,11 @@ DEPENDENCIES bcrypt (~> 3.1.7) bootsnap bootstrap-sass (~> 3.4.1) + bootstrap-will_paginate (= 1.0.0) brakeman capybara debug + faker guard guard-minitest importmap-rails @@ -403,6 +410,7 @@ DEPENDENCIES tzinfo-data web-console webdrivers + will_paginate (= 3.3.1) BUNDLED WITH 2.5.18 diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index 288b9ab..42af950 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -1,3 +1,4 @@ +@import "bootstrap"; /* * This is a manifest file that'll be compiled into application.css, which will include all the files * listed below. diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index f9a80c4..9dc06d4 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,7 +1,7 @@ class UsersController < ApplicationController - before_action :logged_in_user, only: [:index, :edit, :update] - before_action :correct_user, only: [:edit, :update] - + before_action :logged_in_user, only: [:index, :edit, :update, :destroy] + before_action :correct_user, only: [:edit, :update] + before_action :admin_user, only: :destroy def show @user = User.find(params[:id]) @@ -23,11 +23,21 @@ def create end end + def destroy + user = User.find(params[:id]) + if user.destroy + flash[:success] = "User deleted" + else + flash[:danger] = "User could not be deleted" + end + redirect_to users_url, status: :see_other + end + def edit end def index - @users = User.all + @users = User.paginate(page: params[:page], per_page: 10) end def update @@ -62,4 +72,10 @@ def user_params params.require(:user).permit(:name, :email, :password, :password_confirmation) end + def admin_user + unless current_user.admin? + flash[:danger] = "Access Denied." + redirect_to root_url + end + end end diff --git a/app/views/users/_user.html.erb b/app/views/users/_user.html.erb new file mode 100644 index 0000000..2d7fa3d --- /dev/null +++ b/app/views/users/_user.html.erb @@ -0,0 +1,12 @@ +
    + <% @users.each do |user| %> +
  • + <%= link_to gravatar_for( user, size: 50), user %> + <%= link_to user.name, user %> + <% if current_user.admin? && !current_user?(user) %> + | <%= link_to "delete", user, data: { "turbo-method": :delete, + turbo_confirm: "You sure?" } %> + <% end %> +
  • + <% end %> +
diff --git a/app/views/users/index.html.erb b/app/views/users/index.html.erb index c5b5679..e21e9ef 100644 --- a/app/views/users/index.html.erb +++ b/app/views/users/index.html.erb @@ -1,11 +1,7 @@ <% provide(:title, 'All users') %>

All users

+<%= will_paginate %> -
    - <% @users.each do |user| %> -
  • - <%= gravatar_for user, size: 50 %> - <%= link_to user.name, user %> -
  • - <% end %> -
+<%= render "users/user" %> + +<%= will_paginate %> diff --git a/db/migrate/20240918050726_add_admin_to_users.rb b/db/migrate/20240918050726_add_admin_to_users.rb new file mode 100644 index 0000000..60fee8f --- /dev/null +++ b/db/migrate/20240918050726_add_admin_to_users.rb @@ -0,0 +1,5 @@ +class AddAdminToUsers < ActiveRecord::Migration[7.2] + def change + add_column :users, :admin, :boolean, default: false + end +end diff --git a/db/schema.rb b/db/schema.rb index a8c9b02..6799a62 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.2].define(version: 2024_09_15_202915) do +ActiveRecord::Schema[7.2].define(version: 2024_09_18_050726) do create_table "users", force: :cascade do |t| t.string "name" t.string "email" @@ -18,5 +18,6 @@ t.datetime "updated_at", null: false t.string "password_digest" t.string "remember_digest" + t.boolean "admin", default: false end end diff --git a/db/seeds.rb b/db/seeds.rb index 4fbd6ed..1f6bf80 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -1,9 +1,10 @@ -# This file should ensure the existence of records required to run the application in every environment (production, -# development, test). The code here should be idempotent so that it can be executed at any point in every environment. -# The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup). -# -# Example: -# -# ["Action", "Comedy", "Drama", "Horror"].each do |genre_name| -# MovieGenre.find_or_create_by!(name: genre_name) -# end +99.times do |n| + name = Faker::Name.name + email = "example-#{n + 1}@railstutorial.org" + + User.create(name: name, + email: email, + password: "123456", + password_confirmation: "123456", + admin: true) +end diff --git a/test/controllers/users_controller_test.rb b/test/controllers/users_controller_test.rb index e842123..0c67faa 100644 --- a/test/controllers/users_controller_test.rb +++ b/test/controllers/users_controller_test.rb @@ -42,4 +42,30 @@ def setup get users_path assert_redirected_to login_url end + + test "should not allow the admin attribute to be edited via the web" do + log_in_as(@other_user) + assert_not @other_user.admin? + patch user_path(@other_user), params: { + user: { password: "password", + password_confirmation: "password", + admin: true } } + assert_not @other_user.admin? + end + + test "should redirect destroy when not logged in" do + assert_no_difference "User.count" do + delete user_path(@user) + end + assert_response :see_other + assert_redirected_to login_url + end + test "should redirect destroy when logged in as a non-admin" do + log_in_as(@other_user) + assert_no_difference "User.count" do + delete user_path(@user) + end + assert_redirected_to root_url + end + end diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml index 4fa41a6..bffbf3b 100644 --- a/test/fixtures/users.yml +++ b/test/fixtures/users.yml @@ -3,9 +3,27 @@ amr: name: Amr Hossam email: amr@gmail.com + admin: true password_digest: <%= User.digest("password") %> amr2: name: Amr2 email: foo@gmail.com password_digest: <%= User.digest("password") %> + +lana: + name: Lana Kane + email: hands@example.gov + password_digest: <%= User.digest('password') %> + +malory: + name: Malory Archer + email: boss@example.gov + password_digest: <%= User.digest('password') %> + +<% 30.times do |n| %> +user_<%= n %>: + name: <%= "User #{n}" %> + email: <%= "user-#{n}@example.com" %> + password_digest: <%= User.digest('password') %> +<% end %> diff --git a/test/integration/users_index_test.rb b/test/integration/users_index_test.rb new file mode 100644 index 0000000..9977ced --- /dev/null +++ b/test/integration/users_index_test.rb @@ -0,0 +1,36 @@ +require "test_helper" + +class UsersIndexTest < ActionDispatch::IntegrationTest + + def setup + @admin = users(:amr) + @non_admin = users(:amr2) + end + + # TODO check that + + # test "index as admin including pagination and delete links" do + # log_in_as(@admin) + # get users_path + # assert_template "users/index" + # assert_select "div.pagination" + # first_page_of_users = User.paginate(page: 1) + # first_page_of_users.each do |user| + # assert_select "a[href=?]", user_path(user), text: user.name + # unless user == @admin + # assert_select "a[href=?]", user_path(user), text: "delete", method: :delete + # end + # end + # assert_difference "User.count", -1 do + # delete user_path(@non_admin) + # end + # end + + test "index as non-admin" do + log_in_as(@non_admin) + get users_path + assert_template "users/index" + assert_select "a", text: "delete", count: 0 + + end +end