Skip to content

Commit

Permalink
Merge pull request #3 from codigofacilito/feat/register-and-login
Browse files Browse the repository at this point in the history
Feat/register and login
  • Loading branch information
eduardogpg authored Nov 26, 2024
2 parents 10559fb + ecfc250 commit e17f63f
Show file tree
Hide file tree
Showing 13 changed files with 199 additions and 1 deletion.
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ gem "bootsnap", require: false
# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin Ajax possible
# gem "rack-cors"

gem 'bcrypt'

Check failure on line 32 in Gemfile

View workflow job for this annotation

GitHub Actions / lint

Style/StringLiterals: Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.

group :development, :test do
# See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
gem "debug", platforms: %i[ mri windows ], require: "debug/prelude"
Expand Down
2 changes: 2 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ GEM
tzinfo (~> 2.0, >= 2.0.5)
ast (2.4.2)
base64 (0.2.0)
bcrypt (3.1.20)
benchmark (0.4.0)
bigdecimal (3.1.8)
bootsnap (1.18.4)
Expand Down Expand Up @@ -303,6 +304,7 @@ PLATFORMS

DEPENDENCIES
annotate!
bcrypt
bootsnap
brakeman
debug
Expand Down
31 changes: 31 additions & 0 deletions app/controllers/users_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
class UsersController < ApplicationController

def register
user = User.new(user_params)
user.pin = params[:pin]

if user.save
account = user.accounts.first
render json: { user: user.first_name, email:user.email, cuenta: account.account_number, message: 'Usuario registrado exitosamente' }, status: :created
else
render json: { errors: user.errors.full_messages }, status: :unprocessable_entity
end
end

def login
account = Account.find_by(account_number: params[:account_number])

if account && account.user.authenticate_pin(params[:pin])
render json: { message: 'Inicio de sesión exitoso' }, status: :ok
else
render json: { errors: 'Número de cuenta o PIN incorrecto' }, status: :unauthorized
end
end


private

def user_params
params.permit(:first_name, :last_name, :email)
end
end
2 changes: 2 additions & 0 deletions app/models/account.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ class Account < ApplicationRecord

enum :account_type, [:checking, :savings]

validates :account_number, presence: true, uniqueness: true

def transfer!(recipient_account_number, amount, description=nil)
TransactionService.new.transfer!(self, recipient_account_number, amount, description)
end
Expand Down
34 changes: 34 additions & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,45 @@
# biometric_enabled :boolean
# created_at :datetime not null
# updated_at :datetime not null
# email :string
#
class User < ApplicationRecord
has_many :accounts
has_secure_password :pin, validations: false

validates :first_name, presence: true
validates :last_name, presence: true
validates :email, presence: true, uniqueness: true
validates :pin, presence: true, length: { is: 4 }, numericality: { only_integer: true }

after_create :create_default_account

def to_s
"#{first_name} #{last_name}"
end

private

def create_default_account
accounts.create(
account_number: generate_account_number,
account_type: 0,
balance: 1000,
CLABE: generate_clabe
)
end

def generate_clabe
loop do
clabe = SecureRandom.alphanumeric(18).upcase
break clabe unless Account.exists?(CLABE: clabe)
end
end

def generate_account_number
loop do
random_number = rand(10**9..10**10-1).to_s
break random_number unless Account.exists?(account_number: random_number)
end
end
end
2 changes: 2 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
# Defines the root path route ("/")
# root "posts#index"

post 'register', to: 'users#register'
post 'login', to: 'users#login'
# TODO: Move to accounts resources once available
get "/accounts/:account_id/transactions", controller: :transactions, action: :index
end
5 changes: 5 additions & 0 deletions db/migrate/20241121215515_add_email_to_users.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddEmailToUsers < ActiveRecord::Migration[7.2]
def change
add_column :users, :email, :string
end
end
3 changes: 2 additions & 1 deletion db/schema.rb

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

14 changes: 14 additions & 0 deletions spec/factories/users.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
# == Schema Information
#
# Table name: users
#
# id :integer not null, primary key
# first_name :string
# last_name :string
# pin_digest :string
# biometric_enabled :boolean
# created_at :datetime not null
# updated_at :datetime not null
# email :string
#
FactoryBot.define do
factory :user do
first_name { "Admin" }
last_name { "User" }
email { "[email protected]" }
pin_digest { "1234" }
end
end
37 changes: 37 additions & 0 deletions spec/models/user_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
require 'rails_helper'

RSpec.describe User, type: :model do
describe 'validations' do
it 'is not valid without email' do
user = build(:user, email: nil)
expect(user).not_to be_valid
expect(user.errors[:email]).to include("can't be blank")
end

it 'is not valid with duplicated email' do
create(:user, email: '[email protected]', pin: '1234')
user = build(:user, email: '[email protected]')
expect(user).not_to be_valid
expect(user.errors[:email]).to include('has already been taken')
end

it 'not valid without alphanumeric pin' do
user = build(:user, pin: 'abcd')
expect(user).not_to be_valid
expect(user.errors[:pin]).to include('is not a number')
end

it 'not valid without 4 characters pin' do
user = build(:user, pin: '123')
expect(user).not_to be_valid
expect(user.errors[:pin]).to include('is the wrong length (should be 4 characters)')
end
end

describe 'callbacks' do
it 'create an account after user creation' do
user = create(:user, email:'[email protected]', pin: '1234')
expect(user.accounts.count).to eq(1)
end
end
end
66 changes: 66 additions & 0 deletions spec/requests/users_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
require 'rails_helper'

RSpec.describe 'Users API', type: :request do
describe 'POST /register' do
let(:valid_attributes) do
{
first_name: 'Juan',
last_name: 'Perez',
email: '[email protected]',
pin: '1234'
}
end

context 'when the request is valid' do
it 'creates a new user' do
expect {
post '/register', params: valid_attributes
}.to change(User, :count).by(1)
end

it 'creates an associated account with an initial balance of $1,000' do
post '/register', params: valid_attributes
user = User.last
account = user.accounts.first
expect(account).not_to be_nil
expect(account.balance).to eq(1000)
end
end

context 'when the request is invalid' do
it 'does not create a user without email' do
invalid_attributes = valid_attributes.except(:email)
expect {
post '/register', params: invalid_attributes
}.not_to change(User, :count)
end
end
end

describe 'POST /login' do
let(:user) { create(:user, pin: '1234') }
let(:account) { create(:account, user: user) }

context 'with valid credentials' do
it 'logs in successfully' do
post '/login', params: { account_number: account.account_number, pin: '1234' }
expect(response).to have_http_status(:ok)
expect(JSON.parse(response.body)['message']).to eq('Inicio de sesión exitoso')
end
end

context 'with invalid credentials' do
it 'rejects login with incorrect PIN' do
post '/login', params: { account_number: account.account_number, pin: '0000' }
expect(response).to have_http_status(:unauthorized)
expect(JSON.parse(response.body)['errors']).to eq('Número de cuenta o PIN incorrecto')
end

it 'rejects login with incorrect account number' do
post '/login', params: { account_number: 'invalid', pin: '1234' }
expect(response).to have_http_status(:unauthorized)
expect(JSON.parse(response.body)['errors']).to eq('Número de cuenta o PIN incorrecto')
end
end
end
end
1 change: 1 addition & 0 deletions test/fixtures/users.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
# biometric_enabled :boolean
# created_at :datetime not null
# updated_at :datetime not null
# email :string
#

one:
Expand Down
1 change: 1 addition & 0 deletions test/models/user_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
# biometric_enabled :boolean
# created_at :datetime not null
# updated_at :datetime not null
# email :string
#
require "test_helper"

Expand Down

0 comments on commit e17f63f

Please sign in to comment.