Skip to content

Commit

Permalink
Merge pull request #16 from felipejoglar/felipejoglar/add-projects-su…
Browse files Browse the repository at this point in the history
…pport

Add projects support
  • Loading branch information
felipejoglar authored Mar 21, 2024
2 parents 0de68c1 + 3d09dd4 commit 9c0d4db
Show file tree
Hide file tree
Showing 27 changed files with 137 additions and 58 deletions.
4 changes: 0 additions & 4 deletions app/controllers/concerns/authentication.rb
Original file line number Diff line number Diff line change
@@ -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
Expand Down
4 changes: 0 additions & 4 deletions app/controllers/home_controller.rb

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
class PasswordResetController < ApplicationController
class PasswordsController < ApplicationController
skip_before_action :authenticate_user!
before_action :user_by_token, only: [:edit, :update]

Expand All @@ -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
Expand All @@ -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
5 changes: 5 additions & 0 deletions app/controllers/projects_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class ProjectsController < ApplicationController
def index
@projects = Current.user.projects.all
end
end
5 changes: 5 additions & 0 deletions app/models/project.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class Project < ApplicationRecord
belongs_to :user

validates :name, :user_id, presence: true
end
18 changes: 8 additions & 10 deletions app/models/user.rb
Original file line number Diff line number Diff line change
@@ -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/

Expand All @@ -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
15 changes: 15 additions & 0 deletions app/models/user/resettable.rb
Original file line number Diff line number Diff line change
@@ -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
2 changes: 0 additions & 2 deletions app/views/home/index.html.erb

This file was deleted.

4 changes: 2 additions & 2 deletions app/views/landing/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
<h1 class="text-primary-gradient font-bold"><%= t("app_name") %></h1>
</div>
<div>
<a class="btn-primary" href="<%= signin_path %>">
<a class="btn-primary" href="<%= new_signin_path %>">
<%= t("auth.signin") %>
</a>
<a class="btn-primary" href="<%= signup_path %>">
<a class="btn-primary" href="<%= new_signup_path %>">
<%= t("auth.signup") %>
</a>
</div>
Expand Down
File renamed without changes.
File renamed without changes.
7 changes: 7 additions & 0 deletions app/views/projects/index.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<h1>Welcome <%= Current.user.name %></h1>
<%= button_to "Logout", signout_path, method: :delete %>
<ul>
<% @projects.each do |project| %>
<li><h3><%= project.name %></h3></li>
<% end %>
</ul>
2 changes: 1 addition & 1 deletion app/views/sessions/new.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@
<% end %>
</div>
<div class="text-center">
<%= 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" %>
</div>
</div>
2 changes: 1 addition & 1 deletion app/views/user_mailer/password_reset.html.erb
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<%= link_to "Reset your password", password_edit_url(token: params[:token]) %>
<%= link_to "Reset your password", edit_password_url(token: params[:token]) %>
2 changes: 1 addition & 1 deletion app/views/users/new.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,6 @@
<% end %>
</div>
<div class="text-center text-sm mb-4">
<%= 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 %>
</div>
</div>
19 changes: 9 additions & 10 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
9 changes: 9 additions & 0 deletions db/migrate/20240320071340_create_projects.rb
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions db/migrate/20240320083301_add_user_ref_to_projects.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddUserRefToProjects < ActiveRecord::Migration[7.1]
def change
add_reference :projects, :user, null: false, foreign_key: true
end
end
11 changes: 10 additions & 1 deletion db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -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")
Expand All @@ -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")
Expand All @@ -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")
Expand All @@ -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")
Expand Down
4 changes: 2 additions & 2 deletions test/controllers/sessions_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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")
Expand Down
4 changes: 2 additions & 2 deletions test/controllers/users_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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")
Expand Down
9 changes: 9 additions & 0 deletions test/fixtures/projects.yml
Original file line number Diff line number Diff line change
@@ -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
14 changes: 5 additions & 9 deletions test/fixtures/users.yml
Original file line number Diff line number Diff line change
@@ -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"
2 changes: 1 addition & 1 deletion test/mailers/password_reset_mailer_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
11 changes: 11 additions & 0 deletions test/models/project_test.rb
Original file line number Diff line number Diff line change
@@ -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
21 changes: 21 additions & 0 deletions test/models/user_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 9c0d4db

Please sign in to comment.