diff --git a/app/controllers/concerns/authentication.rb b/app/controllers/concerns/authentication.rb index c68312f..e20d830 100644 --- a/app/controllers/concerns/authentication.rb +++ b/app/controllers/concerns/authentication.rb @@ -1,10 +1,6 @@ module Authentication extend ActiveSupport::Concern - included do - helper_method :current_user, :user_signed_in? - end - def sign_in(user) reset_session session[:current_user_id] = user.id diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb deleted file mode 100644 index 95f2992..0000000 --- a/app/controllers/home_controller.rb +++ /dev/null @@ -1,4 +0,0 @@ -class HomeController < ApplicationController - def index - end -end diff --git a/app/controllers/password_reset_controller.rb b/app/controllers/passwords_controller.rb similarity index 69% rename from app/controllers/password_reset_controller.rb rename to app/controllers/passwords_controller.rb index 1de26d5..50693a8 100644 --- a/app/controllers/password_reset_controller.rb +++ b/app/controllers/passwords_controller.rb @@ -1,4 +1,4 @@ -class PasswordResetController < ApplicationController +class PasswordsController < ApplicationController skip_before_action :authenticate_user! before_action :user_by_token, only: [:edit, :update] @@ -8,7 +8,7 @@ def new def create User.find_by(email: params[:user][:email])&.password_reset_requested - redirect_to signin_path, notice: t("auth.password_reset.message.confirmation") + redirect_to new_signin_path, notice: t("auth.password_reset.message.confirmation") end def edit @@ -31,6 +31,6 @@ def password_params def user_by_token @user = User.find_by_token_for(:password_reset, params[:token]) - redirect_to forgot_password_path, notice: t("auth.password_reset.message.expired") unless @user.present? + redirect_to new_forgot_password_path, notice: t("auth.password_reset.message.expired") unless @user.present? end end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb new file mode 100644 index 0000000..defd28a --- /dev/null +++ b/app/controllers/projects_controller.rb @@ -0,0 +1,5 @@ +class ProjectsController < ApplicationController + def index + @projects = Current.user.projects.all + end +end diff --git a/app/models/project.rb b/app/models/project.rb new file mode 100644 index 0000000..a5d888c --- /dev/null +++ b/app/models/project.rb @@ -0,0 +1,5 @@ +class Project < ApplicationRecord + belongs_to :user + + validates :name, :user_id, presence: true +end diff --git a/app/models/user.rb b/app/models/user.rb index 0018cb2..8d482ad 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,6 +1,12 @@ class User < ApplicationRecord + include Resettable + has_secure_password + has_many :projects, dependent: :destroy + + after_create :create_default_project + EMAIL_REQUIREMENTS = URI::MailTo::EMAIL_REGEXP PASSWORD_REQUIREMENTS = /\A.{12,64}\z/ @@ -11,17 +17,9 @@ class User < ApplicationRecord normalizes :email, with: -> (email) { email.strip.downcase } - def password_reset_requested - UserMailer.with(user: self, token: generate_token_for(:password_reset)) - .password_reset - .deliver_later - end - private - RESET_PASSWORD_TOKEN_EXPIRATION = 15.minutes - - generates_token_for :password_reset, expires_in: RESET_PASSWORD_TOKEN_EXPIRATION do - password_salt&.last(12) + def create_default_project + self.projects.create(name: "Inbox") end end diff --git a/app/models/user/resettable.rb b/app/models/user/resettable.rb new file mode 100644 index 0000000..d8ae247 --- /dev/null +++ b/app/models/user/resettable.rb @@ -0,0 +1,15 @@ +module User::Resettable + extend ActiveSupport::Concern + + included do + generates_token_for :password_reset, expires_in: 15.minutes do + password_salt&.last(12) + end + end + + def password_reset_requested + UserMailer.with(user: self, token: generate_token_for(:password_reset)) + .password_reset + .deliver_later + end +end diff --git a/app/views/home/index.html.erb b/app/views/home/index.html.erb deleted file mode 100644 index 172f0df..0000000 --- a/app/views/home/index.html.erb +++ /dev/null @@ -1,2 +0,0 @@ -

Welcome <%= current_user.name %>

-<%= button_to "Logout", signout_path, method: :delete %> \ No newline at end of file diff --git a/app/views/landing/index.html.erb b/app/views/landing/index.html.erb index 6d1451d..4e8b9cf 100644 --- a/app/views/landing/index.html.erb +++ b/app/views/landing/index.html.erb @@ -5,10 +5,10 @@

<%= t("app_name") %>

- + <%= t("auth.signin") %> - + <%= t("auth.signup") %>
diff --git a/app/views/password_reset/edit.html.erb b/app/views/passwords/edit.html.erb similarity index 100% rename from app/views/password_reset/edit.html.erb rename to app/views/passwords/edit.html.erb diff --git a/app/views/password_reset/new.html.erb b/app/views/passwords/new.html.erb similarity index 100% rename from app/views/password_reset/new.html.erb rename to app/views/passwords/new.html.erb diff --git a/app/views/projects/index.html.erb b/app/views/projects/index.html.erb new file mode 100644 index 0000000..d50498e --- /dev/null +++ b/app/views/projects/index.html.erb @@ -0,0 +1,7 @@ +

Welcome <%= Current.user.name %>

+<%= button_to "Logout", signout_path, method: :delete %> + diff --git a/app/views/sessions/new.html.erb b/app/views/sessions/new.html.erb index 8fe4df0..355e2ec 100644 --- a/app/views/sessions/new.html.erb +++ b/app/views/sessions/new.html.erb @@ -20,6 +20,6 @@ <% end %>
- <%= link_to t("auth.sign_in.forgot_password"), forgot_password_path, class: "text-sm" %> + <%= link_to t("auth.sign_in.forgot_password"), new_forgot_password_path, class: "text-sm" %>
\ No newline at end of file diff --git a/app/views/user_mailer/password_reset.html.erb b/app/views/user_mailer/password_reset.html.erb index 788984a..3a3ae5b 100644 --- a/app/views/user_mailer/password_reset.html.erb +++ b/app/views/user_mailer/password_reset.html.erb @@ -1 +1 @@ -<%= link_to "Reset your password", password_edit_url(token: params[:token]) %> \ No newline at end of file +<%= link_to "Reset your password", edit_password_url(token: params[:token]) %> \ No newline at end of file diff --git a/app/views/users/new.html.erb b/app/views/users/new.html.erb index 917cc96..17acc28 100644 --- a/app/views/users/new.html.erb +++ b/app/views/users/new.html.erb @@ -32,6 +32,6 @@ <% end %>
- <%= t("auth.sign_up.already_signed_up") %> <%= link_to t("auth.signin"), signin_path %> + <%= t("auth.sign_up.already_signed_up") %> <%= link_to t("auth.signin"), new_signin_path %>
\ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index d969e1d..0e906fd 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -8,17 +8,16 @@ # Defines the root path route ("/") root "landing#index" - post "signup", to: "users#create" - get "signup", to: "users#new" + resource :signup, only: %i[ create new ], controller: "users" + resource :signin, only: %i[ create new ], controller: "sessions" + resource :signout, only: :destroy, controller: "sessions" - post "signin", to: "sessions#create" - get "signin", to: "sessions#new" - delete "signout", to: "sessions#destroy" + resource :forgot_password, only: :new, controller: "passwords" + resource :password, only: %i[ create update edit ], controller: "passwords" - get "forgot_password", to: "password_reset#new" - post "password", to: "password_reset#create" - get "password/edit", to: "password_reset#edit" - patch "password", to: "password_reset#update" + scope ":user_id", constraints: { user_id: /\d+/ } do + get "/", to: "projects#index", as: :home - get ":user_id", to: "home#index", as: :home + resources :projects + end end diff --git a/db/migrate/20240320071340_create_projects.rb b/db/migrate/20240320071340_create_projects.rb new file mode 100644 index 0000000..e2e511b --- /dev/null +++ b/db/migrate/20240320071340_create_projects.rb @@ -0,0 +1,9 @@ +class CreateProjects < ActiveRecord::Migration[7.1] + def change + create_table :projects do |t| + t.string :name + + t.timestamps + end + end +end diff --git a/db/migrate/20240320083301_add_user_ref_to_projects.rb b/db/migrate/20240320083301_add_user_ref_to_projects.rb new file mode 100644 index 0000000..9ff9226 --- /dev/null +++ b/db/migrate/20240320083301_add_user_ref_to_projects.rb @@ -0,0 +1,5 @@ +class AddUserRefToProjects < ActiveRecord::Migration[7.1] + def change + add_reference :projects, :user, null: false, foreign_key: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 1c5310a..4fd7b66 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,10 +10,18 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_02_01_003802) do +ActiveRecord::Schema[7.1].define(version: 2024_03_20_083301) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" + create_table "projects", force: :cascade do |t| + t.string "name" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.bigint "user_id", null: false + t.index ["user_id"], name: "index_projects_on_user_id" + end + create_table "users", force: :cascade do |t| t.string "name", null: false t.string "email", null: false @@ -23,4 +31,5 @@ t.index ["email"], name: "index_users_on_email", unique: true end + add_foreign_key "projects", "users" end diff --git a/test/controllers/password_reset_controller_test.rb b/test/controllers/passwords_controller_test.rb similarity index 88% rename from test/controllers/password_reset_controller_test.rb rename to test/controllers/passwords_controller_test.rb index 6415c3c..9e88394 100644 --- a/test/controllers/password_reset_controller_test.rb +++ b/test/controllers/passwords_controller_test.rb @@ -1,13 +1,13 @@ require "test_helper" -class PasswordResetControllerTest < ActionDispatch::IntegrationTest +class PasswordsControllerTest < ActionDispatch::IntegrationTest setup do @name = "Felipe" @token = create_user_with(name: @name).generate_token_for(:password_reset) end test "requesting to create a new password" do - get forgot_password_url + get new_forgot_password_url assert_response :ok assert_select 'h2', I18n .t("auth.forgot_password.title") @@ -20,7 +20,7 @@ class PasswordResetControllerTest < ActionDispatch::IntegrationTest end test "updating a password" do - get password_edit_url, params: { token: @token } + get edit_password_url, params: { token: @token } assert_response :ok assert_select 'h2', I18n.t("auth.password_reset.title") @@ -34,7 +34,7 @@ class PasswordResetControllerTest < ActionDispatch::IntegrationTest end test "failing to update a password with invalid token" do - get password_edit_url, params: { token: @token } + get edit_password_url, params: { token: @token } assert_response :ok assert_select 'h2', I18n.t("auth.password_reset.title") @@ -47,7 +47,7 @@ class PasswordResetControllerTest < ActionDispatch::IntegrationTest end test "failing to update a password with invalid password" do - get password_edit_url, params: { token: @token } + get edit_password_url, params: { token: @token } assert_response :ok assert_select 'h2', I18n.t("auth.password_reset.title") diff --git a/test/controllers/sessions_controller_test.rb b/test/controllers/sessions_controller_test.rb index 3993be0..659594c 100644 --- a/test/controllers/sessions_controller_test.rb +++ b/test/controllers/sessions_controller_test.rb @@ -6,7 +6,7 @@ class SessionsControllerTest < ActionDispatch::IntegrationTest email = "felipe@taskodoro.com" password = "a_valid_password" - get signin_url + get new_signin_url assert_response :ok assert_select 'h2', I18n.t("auth.sign_in.title") @@ -25,7 +25,7 @@ class SessionsControllerTest < ActionDispatch::IntegrationTest email = "felipe@taskodoro.com" password = "a_valid_password" - get signin_url + get new_signin_url assert_response :ok assert_select 'h2', I18n.t("auth.sign_in.title") diff --git a/test/controllers/users_controller_test.rb b/test/controllers/users_controller_test.rb index 0205b58..6d6c348 100644 --- a/test/controllers/users_controller_test.rb +++ b/test/controllers/users_controller_test.rb @@ -3,7 +3,7 @@ class UsersControllerTest < ActionDispatch::IntegrationTest test "creating a new user" do - get signup_url + get new_signup_url assert_response :ok assert_select 'h2', I18n.t("auth.sign_up.title") @@ -17,7 +17,7 @@ class UsersControllerTest < ActionDispatch::IntegrationTest end test "failing to create a new user" do - get signup_url + get new_signup_url assert_response :ok assert_select 'h2', I18n.t("auth.sign_up.title") diff --git a/test/fixtures/projects.yml b/test/fixtures/projects.yml new file mode 100644 index 0000000..6ef7f2f --- /dev/null +++ b/test/fixtures/projects.yml @@ -0,0 +1,9 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +inbox: + name: "Inbox" + user_id: 1 + +secret: + name: "Secret project" + user_id: 1 diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml index 58da1ef..f7e1319 100644 --- a/test/fixtures/users.yml +++ b/test/fixtures/users.yml @@ -1,11 +1,7 @@ # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html -one: - name: MyString - email: aUniqueEmail - password_digest: MyString - -two: - name: MyString - email: anotherUniqueEmail - password_digest: MyString +felipe: + id: 1 + name: "Felipe" + email: "felipejoglar@taskodoro.com" + password_digest: "a_password_digest" diff --git a/test/mailers/password_reset_mailer_test.rb b/test/mailers/password_reset_mailer_test.rb index 0964a37..fb2a46f 100644 --- a/test/mailers/password_reset_mailer_test.rb +++ b/test/mailers/password_reset_mailer_test.rb @@ -2,7 +2,7 @@ class UserMailerTest < ActionMailer::TestCase - test "password_reset sends the token in the URL" do + test "passwords sends the token in the URL" do email = "an_email@email.com" token = "a_token" diff --git a/test/models/project_test.rb b/test/models/project_test.rb new file mode 100644 index 0000000..1817bca --- /dev/null +++ b/test/models/project_test.rb @@ -0,0 +1,11 @@ +require "test_helper" + +class ProjectTest < ActiveSupport::TestCase + + test "project attributes must not be empty" do + project = Project.new + assert project.invalid?, message: "Project without params should be invalid" + assert project.errors[:name].any?, message: "Expected name to be present" + assert project.errors[:user_id].any?, message: "Expected project to belong to an user" + end +end diff --git a/test/models/user_test.rb b/test/models/user_test.rb index 76069d2..b703021 100644 --- a/test/models/user_test.rb +++ b/test/models/user_test.rb @@ -52,6 +52,27 @@ class UserTest < ActiveSupport::TestCase assert_equal("an_email@email.com", user.email) end + test "projects are deleted along with user" do + user = valid_user + user.save! + user.projects.create(name: "Project 1") + user.projects.create(name: "Project 2") + + assert user.projects.any? + + user.destroy! + + assert_equal 0, user.projects.size + end + + test "creates default project after creation" do + user = valid_user + user.save! + + assert_equal 1, user.projects.size + assert_equal "Inbox", user.projects.first!.name + end + private MIN_PASSWORD_LENGTH = 12