diff --git a/Gemfile b/Gemfile index d1d27c6..d916461 100644 --- a/Gemfile +++ b/Gemfile @@ -33,7 +33,7 @@ gem "jbuilder" # gem "kredis" # Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword] -# gem "bcrypt", "~> 3.1.7" +gem "bcrypt", "~> 3.1.7" # Windows does not include zoneinfo files, so bundle the tzinfo-data gem gem "tzinfo-data", platforms: %i[ windows jruby ] diff --git a/Gemfile.lock b/Gemfile.lock index 4caa16a..052182c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -78,6 +78,7 @@ GEM addressable (2.8.7) public_suffix (>= 2.0.2, < 7.0) base64 (0.2.0) + bcrypt (3.1.20) bigdecimal (3.1.8) bindex (0.8.1) bootsnap (1.18.4) @@ -245,6 +246,7 @@ PLATFORMS x86_64-linux DEPENDENCIES + bcrypt (~> 3.1.7) bootsnap capybara debug diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 09705d1..039cdee 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,2 +1,18 @@ class ApplicationController < ActionController::Base + helper_method :current_user, :logged_in? + + def current_user + @current_user ||= User.find_by(id: session[:user_id]) if session[:user_id] + end + + def logged_in? + !current_user.nil? + end + + def require_user + unless logged_in? + flash[:alert] = "You must be logged in first. Please visit the signup page to create an account." + redirect_to login_path + end + end end diff --git a/app/controllers/articles_controller.rb b/app/controllers/articles_controller.rb index 151eac9..61fa451 100644 --- a/app/controllers/articles_controller.rb +++ b/app/controllers/articles_controller.rb @@ -1,5 +1,5 @@ class ArticlesController < ApplicationController - http_basic_authenticate_with name: "dhh", password: "secret", except: [:index, :show] + before_action :require_user, except: [:show, :index] def index @articles = Article.all @@ -15,6 +15,7 @@ class ArticlesController < ApplicationController def create @article = Article.new(article_params) + @article.user_id = current_user.id if @article.save redirect_to @article diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index 9165327..9ea649c 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -1,21 +1,31 @@ class CommentsController < ApplicationController - http_basic_authenticate_with name: "dhh", password: "secret", only: :destroy + before_action :require_user def create @article = Article.find(params[:article_id]) - @comment = @article.comments.create(comment_params) + @comment = @article.comments.new(comment_params) + @comment.commenter = current_user.username + + if @comment.save + flash[:notice] = "Comment added successfully." + else + flash[:alert] = "Failed to add comment." + end + redirect_to article_path(@article) end def destroy @article = Article.find(params[:article_id]) @comment = @article.comments.find(params[:id]) - comment.destroy + if @article.user_id == current_user.id || @comment.commenter == current_user.username + @comment.destroy + end redirect_to article_path(@article), status: :see_other end private def comment_params - params.require(:comment).permit(:commenter, :body, :status) + params.require(:comment).permit(:body, :status) end end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb new file mode 100644 index 0000000..4e77811 --- /dev/null +++ b/app/controllers/sessions_controller.rb @@ -0,0 +1,23 @@ +class SessionsController < ApplicationController + def new + end + + def create + user = User.find_by(email: params[:email].downcase) + if user && user.authenticate(params[:password]) + session[:user_id] = user.id + flash[:notice] = "Logged in successfully." + redirect_to root_path + else + flash[:alert] = "Invalid email or password" + render :new + end + end + + def destroy + session[:user_id] = nil + reset_session + flash[:notice] = "Logged out successfully." + redirect_to root_path + end +end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb new file mode 100644 index 0000000..ea252dc --- /dev/null +++ b/app/controllers/users_controller.rb @@ -0,0 +1,22 @@ +class UsersController < ApplicationController + def new + @user = User.new + end + + def create + @user = User.new(user_params) + if @user.save + session[:user_id] = @user.id + flash[:notice] = "Welcome! You have successfully signed up." + redirect_to root_path + else + render :new + end + end + + private + + def user_params + params.require(:user).permit(:username, :email, :password, :password_confirmation) + end +end diff --git a/app/helpers/sessions_helper.rb b/app/helpers/sessions_helper.rb new file mode 100644 index 0000000..309f8b2 --- /dev/null +++ b/app/helpers/sessions_helper.rb @@ -0,0 +1,2 @@ +module SessionsHelper +end diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb new file mode 100644 index 0000000..2310a24 --- /dev/null +++ b/app/helpers/users_helper.rb @@ -0,0 +1,2 @@ +module UsersHelper +end diff --git a/app/models/article.rb b/app/models/article.rb index 57ec8c1..f4ff4d5 100644 --- a/app/models/article.rb +++ b/app/models/article.rb @@ -1,7 +1,7 @@ class Article < ApplicationRecord include Visible - belongs_to: :user + belongs_to :user has_many :comments, dependent: :destroy validates :title, presence: true diff --git a/app/models/user.rb b/app/models/user.rb index 9ecd5e0..3c0d0a3 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,5 +1,7 @@ class User < ApplicationRecord - has_many :articles, dependent: :destroy - + has_many :articles, dependent: :destroy has_secure_password + + validates :email, presence: true, uniqueness: true + validates :password, presence: true, length: { minimum: 6 } end diff --git a/app/views/articles/index.html.erb b/app/views/articles/index.html.erb index 7eb810a..8fb5867 100644 --- a/app/views/articles/index.html.erb +++ b/app/views/articles/index.html.erb @@ -1,3 +1,12 @@ +<% if logged_in? %> +

Logged in as <%= current_user.username %>

+

<%= link_to "Log out", logout_path, data: { + turbo_method: :delete + } %>

+<% else %> +

<%= link_to "Log in", login_path %> or <%= link_to "Sign up", signup_path %>

+<% end %> +

Articles!

Our blog has <%= Article.public_count %> articles and counting! diff --git a/app/views/articles/show.html.erb b/app/views/articles/show.html.erb index bbe79cd..6c4c52d 100644 --- a/app/views/articles/show.html.erb +++ b/app/views/articles/show.html.erb @@ -2,13 +2,15 @@

<%= @article.body %>

- +<% if logged_in? && @article.user_id == current_user.id %> + +<% end %>

Comments

<%= render @article.comments %> diff --git a/app/views/comments/_form.html.erb b/app/views/comments/_form.html.erb index 0dde6b7..9cd21c0 100644 --- a/app/views/comments/_form.html.erb +++ b/app/views/comments/_form.html.erb @@ -1,17 +1,18 @@ -<%= form_with model: [ @article, @article.comments.build ] do |form| %> -

- <%= form.label :commenter %>
- <%= form.text_field :commenter %>
-

-

- <%= form.label :body %>
- <%= form.text_area :body %>
-

-

- <%= form.label :status %>
- <%= form.select :status, Visible::VALID_STATUSES, selected: 'public' %>
-

-

- <%= form.submit %>
-

+<% if logged_in? %> + <%= form_with model: [ @article, @article.comments.build ] do |form| %> +

+ <%= form.label :body %>
+ <%= form.text_area :body %>
+

+

+ <%= form.label :status %>
+ <%= form.select :status, Visible::VALID_STATUSES, selected: 'public' %>
+

+

+ <%= form.submit %>
+

+ <% end %> +<% else %> + <%= link_to "Sign up", signup_path %> or + <%= link_to "Log in", login_path %> <% end %> \ No newline at end of file diff --git a/app/views/sessions/create.html.erb b/app/views/sessions/create.html.erb new file mode 100644 index 0000000..c251174 --- /dev/null +++ b/app/views/sessions/create.html.erb @@ -0,0 +1,2 @@ +

Sessions#create

+

Find me in app/views/sessions/create.html.erb

diff --git a/app/views/sessions/destroy.html.erb b/app/views/sessions/destroy.html.erb new file mode 100644 index 0000000..d75237d --- /dev/null +++ b/app/views/sessions/destroy.html.erb @@ -0,0 +1,2 @@ +

Sessions#destroy

+

Find me in app/views/sessions/destroy.html.erb

diff --git a/app/views/sessions/new.html.erb b/app/views/sessions/new.html.erb new file mode 100644 index 0000000..42c1913 --- /dev/null +++ b/app/views/sessions/new.html.erb @@ -0,0 +1,17 @@ +

Log in

+ +<%= form_with url: login_path, local: true do |form| %> +
+ <%= form.label :email %> + <%= form.email_field :email, autofocus: true, autocomplete: "email" %> +
+ +
+ <%= form.label :password %> + <%= form.password_field :password, autocomplete: "current-password" %> +
+ +
+ <%= form.submit "Log in" %> +
+<% end %> \ No newline at end of file diff --git a/app/views/users/new.html.erb b/app/views/users/new.html.erb new file mode 100644 index 0000000..a0ea1f8 --- /dev/null +++ b/app/views/users/new.html.erb @@ -0,0 +1,38 @@ +

Sign up

+ +<%= form_with model: @user, local: true do |form| %> + <% if @user.errors.any? %> +
+

<%= pluralize(@user.errors.count, "error") %> prohibited this user from being saved:

+ +
+ <%end%> + +
+ <%= form.label :username %> + <%= form.text_field :username, auto_focus: true, autocomplete: "username" %> +
+ +
+ <%= form.label :email %> + <%= form.email_field :email, autocomplete: "email" %> +
+ +
+ <%= form.label :password %> + <%= form.password_field :password, autocomplete: "new-password" %> +
+ +
+ <%= form.label :password_confirmation %> + <%= form.password_field :password_confirmation %> +
+ +
+ <%= form.submit "Sign up"%> +
+<% end %> \ No newline at end of file diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb new file mode 100644 index 0000000..dd95d9d --- /dev/null +++ b/config/initializers/session_store.rb @@ -0,0 +1 @@ +Rails.application.config.session_store :cookie_store, key: '_blog_session' diff --git a/config/routes.rb b/config/routes.rb index 03ae6ac..fbc8bc7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,7 @@ Rails.application.routes.draw do + get 'sessions/new' + get 'sessions/create' + get 'sessions/destroy' root "articles#index" resources :articles do @@ -10,5 +13,12 @@ Rails.application.routes.draw do # Can be used by load balancers and uptime monitors to verify that the app is live. get "up" => "rails/health#show", as: :rails_health_check - # Defines the root path route ("/") + resources :users, except: [:new] + + get "signup", to: "users#new" + post "signup", to: "users#create" + + get "login", to: "sessions#new" + post "login", to: "sessions#create" + delete "logout", to: "sessions#destroy" end diff --git a/test/controllers/sessions_controller_test.rb b/test/controllers/sessions_controller_test.rb new file mode 100644 index 0000000..4ebb021 --- /dev/null +++ b/test/controllers/sessions_controller_test.rb @@ -0,0 +1,18 @@ +require "test_helper" + +class SessionsControllerTest < ActionDispatch::IntegrationTest + test "should get new" do + get sessions_new_url + assert_response :success + end + + test "should get create" do + get sessions_create_url + assert_response :success + end + + test "should get destroy" do + get sessions_destroy_url + assert_response :success + end +end diff --git a/test/controllers/users_controller_test.rb b/test/controllers/users_controller_test.rb new file mode 100644 index 0000000..c6a205d --- /dev/null +++ b/test/controllers/users_controller_test.rb @@ -0,0 +1,13 @@ +require "test_helper" + +class UsersControllerTest < ActionDispatch::IntegrationTest + test "should get new" do + get users_new_url + assert_response :success + end + + test "should get create" do + get users_create_url + assert_response :success + end +end