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") %>
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 %>
+
+ <% @projects.each do |project| %>
+ <%= project.name %>
+ <% end %>
+
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