Skip to content

Commit

Permalink
Merge branch 'dev' into property
Browse files Browse the repository at this point in the history
  • Loading branch information
sambeck87 committed May 9, 2023
2 parents f816e9f + 5218b2a commit aebc2de
Show file tree
Hide file tree
Showing 20 changed files with 759 additions and 468 deletions.
12 changes: 11 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby '>= 3.1.0'

gem 'devise'
# Use CanCanCan for authorization
gem 'cancancan'

# Use Json Web Token (JWT) for token based authentication
gem 'jwt'

# Use ActiveModel has_secure_password
gem 'bcrypt', '~> 3.1.7'

# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
gem 'rails', '~> 7.0.4', '>= 7.0.4.3'
Expand Down Expand Up @@ -41,8 +48,11 @@ gem 'rack-cors'
group :development, :test do
# See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
gem 'debug', platforms: %i[mri mingw x64_mingw]
gem 'faker'
gem 'rails-controller-testing'
gem 'rspec-rails', '~> 6.0.0'
gem 'rubocop', '>= 1.0', '< 2.0'
gem 'table_print'
end

group :development do
Expand Down
28 changes: 15 additions & 13 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -71,20 +71,17 @@ GEM
bootsnap (1.16.0)
msgpack (~> 1.2)
builder (3.2.4)
cancancan (3.5.0)
concurrent-ruby (1.2.2)
crass (1.0.6)
date (3.3.3)
debug (1.7.2)
irb (>= 1.5.0)
reline (>= 0.3.1)
devise (4.9.2)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 4.1.0)
responders
warden (~> 1.2.3)
diff-lcs (1.5.0)
erubi (1.12.0)
faker (3.2.0)
i18n (>= 1.8.11, < 2)
globalid (1.1.0)
activesupport (>= 5.0)
i18n (1.13.0)
Expand All @@ -93,6 +90,7 @@ GEM
irb (1.6.4)
reline (>= 0.3.0)
json (2.6.3)
jwt (2.7.0)
loofah (2.20.0)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
Expand All @@ -118,7 +116,6 @@ GEM
nio4r (2.5.9)
nokogiri (1.14.3-x86_64-linux)
racc (~> 1.4)
orm_adapter (0.5.0)
parallel (1.23.0)
parser (3.2.2.1)
ast (~> 2.4.1)
Expand All @@ -145,6 +142,10 @@ GEM
activesupport (= 7.0.4.3)
bundler (>= 1.15.0)
railties (= 7.0.4.3)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1)
activesupport (>= 5.0.1.rc1)
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
nokogiri (>= 1.6)
Expand All @@ -162,9 +163,6 @@ GEM
regexp_parser (2.8.0)
reline (0.3.3)
io-console (~> 0.5)
responders (3.1.0)
actionpack (>= 5.2)
railties (>= 5.2)
rexml (3.2.5)
rspec-core (3.12.2)
rspec-support (~> 3.12.0)
Expand Down Expand Up @@ -196,13 +194,12 @@ GEM
rubocop-ast (1.28.1)
parser (>= 3.2.1.0)
ruby-progressbar (1.13.0)
table_print (1.5.7)
thor (1.2.1)
timeout (0.3.2)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unicode-display_width (2.4.2)
warden (1.2.9)
rack (>= 2.0.9)
websocket-driver (0.7.5)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
Expand All @@ -212,15 +209,20 @@ PLATFORMS
x86_64-linux

DEPENDENCIES
bcrypt (~> 3.1.7)
bootsnap
cancancan
debug
devise
faker
jwt
pg (~> 1.1)
puma (~> 5.0)
rack-cors
rails (~> 7.0.4, >= 7.0.4.3)
rails-controller-testing
rspec-rails (~> 6.0.0)
rubocop (>= 1.0, < 2.0)
table_print
tzinfo-data

RUBY VERSION
Expand Down
29 changes: 29 additions & 0 deletions app/controllers/api/v1/authentication_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
class Api::V1::AuthenticationController < ApplicationController
before_action :authenticate_user, only: [:current_user]

def sign_in
unless params[:email] && params[:password]
return render json: { success: false, error: 'Invalid request! Missing information.' },
status: :bad_request
end

user = User.find_by(email: params[:email])
if user&.authenticate(params[:password])
user.password_digest = nil
token = JwtToken.sign({ user_id: user.id })
render json: {
success: true,
data: {
user:,
accessToken: token
}
}, status: :ok
else
render json: { success: false, error: 'Invalid email or password' }, status: :unauthorized
end
end

def current_user
render json: { success: true, data: @current_user }, status: :ok
end
end
63 changes: 63 additions & 0 deletions app/controllers/api/v1/users_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
class Api::V1::UsersController < ApplicationController
before_action :authenticate_user, except: :create
load_and_authorize_resource
rescue_from ActiveRecord::RecordNotFound, with: :user_not_found
before_action :set_user, only: %i[show update destroy]

# GET /users
def index
@users = User.all

render json: @users.as_json(except: [:password_digest])
end

# GET /users/1
def show
render json: @user.as_json(except: [:password_digest])
end

# POST /users
def create
user_params[:role] = 'user'

@user = User.new(user_params)

if @user.save
render json: @user.as_json(except: [:password_digest]), status: :created
else
render json: @user.errors, status: :unprocessable_entity
end
end

# PATCH/PUT /users/1
def update
if @user.update(user_params)
render json: @user.as_json(except: [:password_digest])
else
render json: @user.errors, status: :unprocessable_entity
end
end

# DELETE /users/1
def destroy
@user.destroy

render json: { message: 'User deleted successfully' }, status: :no_content
end

private

# Use callbacks to share common setup or constraints between actions.
def user_not_found
render json: { error: "No user with id #{params[:id]}" }, status: :not_found
end

def set_user
@user = User.find(params[:id])
end

# Only allow a list of trusted parameters through.
def user_params
params.require(:user).permit(:name, :email, :role, :password, :password_confirmation, :avatar)
end
end
37 changes: 37 additions & 0 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
@@ -1,2 +1,39 @@
class ApplicationController < ActionController::API
include JwtToken
include CanCan::ControllerAdditions

rescue_from CanCan::AccessDenied, with: :unauthorized_user
def not_found
render json: { error: 'Route not found' }, status: :not_found
end

protected

def authenticate_user
header = request.headers['Authorization']
token = header.split.last if header

begin
@decoded = JwtToken.verify(token)

curr_time = Time.now
if curr_time > @decoded[:exp]
return render json: { success: false, error: 'Invalid token!' },
status: :unauthorized
end

@current_user = User.find(@decoded[:user_id])
@current_user.password_digest = nil
rescue ActiveRecord::RecordNotFound => e
render json: { success: false, error: e.message }, status: :unauthorized
rescue JWT::DecodeError => _e
render json: { success: false, error: 'Invalid token!' }, status: :unauthorized
end
end

attr_reader :current_user

def unauthorized_user(_exception)
render json: { success: false, error: 'Unauthorize access denied!' }, status: :forbidden
end
end
16 changes: 16 additions & 0 deletions app/controllers/concerns/jwt_token.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
require 'jwt'

module JwtToken
extend ActiveSupport::Concern
SECRET_KEY = Rails.application.secrets.access_token_key.to_s || 'this is a demo key'

def self.sign(payload, exp = 1.days.from_now)
payload[:exp] = exp.to_i
JWT.encode(payload, SECRET_KEY)
end

def self.verify(token)
decoded = JWT.decode(token, SECRET_KEY)[0]
HashWithIndifferentAccess.new decoded
end
end
16 changes: 16 additions & 0 deletions app/models/ability.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
class Ability
include CanCan::Ability

def initialize(user)
user ||= User.new # guest user (not logged in)
if user.admin?
can :manage, :all
can :manage, User
else
can :create, User
can :show, User, id: user.id
can :update, User, id: user.id
can :destroy, User, id: user.id
end
end
end
31 changes: 24 additions & 7 deletions app/models/user.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,28 @@
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :property

validates :password, confirmation: true
validates :name, presence: true
validates :avatar, presence: true
validates :role, inclusion: { in: %w[user admin] }
validates :name, presence: { message: "Name can't be blank" },
length: { maximum: 50, message: 'Name is too long (maximum is %<count>s characters)' }
validates :email, presence: { message: "Email can't be blank" },
length: { maximum: 255, message: 'Email is too long (maximum is %<count>s characters)' },
format: { with: URI::MailTo::EMAIL_REGEXP, message: 'Email format is invalid' },
uniqueness: { case_sensitive: false, message: 'Email has already been taken' }
validates :role, inclusion: { in: %w[user admin], message: "Role must be 'user' or 'admin'" }

has_secure_password

validates :password, presence: { message: "Password can't be blank" }, on: :create

validate :password_confirmation_matches_password, on: :create

def password_confirmation_matches_password
return unless password_confirmation != password

errors.add(:password_confirmation, "Password confirmation doesn't match password")
end

def admin?
role == 'admin'
end
end
1 change: 0 additions & 1 deletion config/environments/development.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,5 +62,4 @@

# Uncomment if you wish to allow Action Cable access from any origin.
# config.action_cable.disable_request_forgery_protection = true
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
end
Loading

0 comments on commit aebc2de

Please sign in to comment.