Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env.development.sample
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ DATABASE_URL=mysql2://root:@127.0.0.1:3306/sample?reconnect=true
MAIL_URL=smtp://127.0.0.1:1025
SYSTEM_EMAIL=support@sample.com
SITE_URL=http://localhost:3000/
HMAC_SECRET=ZxW8go9hz3ETCSfxFxpwSkYg_602gOPKearsf6DsxgY
REDIS_URL=redis://localhost:6379
2 changes: 2 additions & 0 deletions .env.test.sample
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ DATABASE_URL=mysql2://root:@127.0.0.1:3306/sample_test?reconnect=true
MAIL_URL=smtp://127.0.0.1:1025
SYSTEM_EMAIL=support@sample.com
SITE_URL=http://localhost:3000/
HMAC_SECRET=ZxW8go9hz3ETCSfxFxpwSkYg_602gOPKearsf6DsxgY
REDIS_URL=redis://localhost:6379
5 changes: 5 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,15 @@ gem 'hanami-validations', '0.6.0' # form validation
gem 'dry-validation', '0.10.4' # validation methods for reform
gem 'ability_list', '0.0.4'
gem 'activesupport', '5.0.0'
gem 'bcrypt', '3.1.11' # encryption
gem 'jwt', '1.5.6' # Json Web Token
gem 'sidekiq', '4.2.9' # handle background jobs
gem 'redis', '3.3.3'

group :development, :test do
gem 'awesome_print', '1.7.0'
gem 'pry', '0.10.4'
gem 'letter_opener', '1.4.1'
end

group :test do
Expand Down
22 changes: 21 additions & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ GEM
descendants_tracker (~> 0.0.4)
ice_nine (~> 0.11.0)
thread_safe (~> 0.3, >= 0.3.1)
bcrypt (3.1.11)
builder (3.2.2)
coderay (1.1.1)
coercible (1.0.0)
descendants_tracker (~> 0.0.1)
concurrent-ruby (1.0.4)
connection_pool (2.2.1)
crack (0.4.3)
safe_yaml (~> 1.0.0)
daemons (1.2.4)
Expand Down Expand Up @@ -90,6 +92,11 @@ GEM
i18n (0.7.0)
ice_nine (0.11.2)
inflecto (0.0.2)
jwt (1.5.6)
launchy (2.4.3)
addressable (~> 2.3)
letter_opener (1.4.1)
launchy (~> 2.2)
listen (3.1.5)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
Expand Down Expand Up @@ -118,12 +125,15 @@ GEM
rack (>= 0.4)
rack-indifferent (1.1.0)
rack (>= 1.5)
rack-protection (1.5.3)
rack
rack-test (0.6.3)
rack (>= 1.0)
rake (11.2.2)
rb-fsevent (0.9.8)
rb-inotify (0.9.7)
ffi (>= 0.5.0)
redis (3.3.3)
rerun (0.11.0)
listen (~> 3.0)
rspec (3.5.0)
Expand All @@ -142,6 +152,11 @@ GEM
ruby_dep (1.5.0)
safe_yaml (1.0.4)
sequel (4.40.0)
sidekiq (4.2.9)
concurrent-ruby (~> 1.0)
connection_pool (~> 2.2, >= 2.2.0)
rack-protection (>= 1.5.0)
redis (~> 3.2, >= 3.2.1)
slop (3.6.0)
thin (1.7.0)
daemons (~> 1.0, >= 1.0.9)
Expand Down Expand Up @@ -170,6 +185,7 @@ DEPENDENCIES
ability_list (= 0.0.4)
activesupport (= 5.0.0)
awesome_print (= 1.7.0)
bcrypt (= 3.1.11)
database_cleaner (= 1.5.3)
dry-validation (= 0.10.4)
factory_girl (= 4.7.0)
Expand All @@ -180,16 +196,20 @@ DEPENDENCIES
grape-swagger (= 0.25.1)
grape-swagger-entity (= 0.1.5)
hanami-validations (= 0.6.0)
jwt (= 1.5.6)
letter_opener (= 1.4.1)
mail (= 2.6.4)
mysql2 (= 0.4.5)
pry (= 0.10.4)
rack (= 1.6.4)
rack-indifferent (= 1.1)
rack-test (= 0.6.3)
rake (= 11.2.2)
redis (= 3.3.3)
rerun (= 0.11.0)
rspec (= 3.5.0)
sequel (= 4.40.0)
sidekiq (= 4.2.9)
thin (= 1.7.0)
uuidtools (= 2.1.5)
vcr (= 3.0.3)
Expand All @@ -199,4 +219,4 @@ RUBY VERSION
ruby 2.3.3p222

BUNDLED WITH
1.13.6
1.14.5
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ install:
run:
bundle exec rerun -b --pattern '{Gemfile,Gemfile.lock,.gems,.bundle,.env*,config.ru,**/*.{rb,ru,yml}}' -- thin start --port=3000 --threaded

worker:
bundle exec sidekiq -C ./application/config/yaml/sidekiq.yml

console:
bundle exec pry -r ./application/api

Expand Down
22 changes: 18 additions & 4 deletions application/api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
require 'bundler'
Bundler.setup :default, RACK_ENV
require 'rack/indifferent'
require 'sidekiq'
require 'grape'
require 'grape/batch'
# Initialize the application so we can add all our components to it
Expand All @@ -21,20 +22,34 @@ class Api < Grape::API; end
# Include all config files
require 'config/sequel'
require 'config/hanami'
require 'config/mail'
require 'config/grape'

# require some global libs
require 'lib/core_ext'
require 'lib/time_formats'
require 'lib/io'
require 'lib/pretty_logger'

# load active support helpers
require 'active_support'
require 'active_support/core_ext'

# require authentication libs
require 'jwt'
require 'bcrypt'

# require all models
Dir['./application/models/*.rb'].each { |rb| require rb }

# require all validators
Dir['./application/api_validators/**/*.rb'].each { |rb| require rb }

# require all mailers
require './application/mailers/mailer_base.rb'
Dir['./application/mailers/**/*.rb'].each { |rb| require rb }


Dir['./application/api_helpers/**/*.rb'].each { |rb| require rb }
class Api < Grape::API
version 'v1.0', using: :path
Expand All @@ -50,14 +65,13 @@ class Api < Grape::API
error! ret, 400
end

logger PrettyLogger.new

helpers SharedParams
helpers ApiResponse
helpers ApiFormat
include Auth

before do
authenticate!
end

Dir['./application/api_entities/**/*.rb'].each { |rb| require rb }
Dir['./application/api/**/*.rb'].each { |rb| require rb }

Expand Down
99 changes: 96 additions & 3 deletions application/api/users.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,106 @@
class Api
resource :users do

params do
includes :basic_search
end

get do
users = SEQUEL_DB[:users].all
{
data: users
}
present users, with: Api::Entities::User
end

desc "Creates a user"
params do
requires :first_name, type: String, desc: "User's first name"
requires :last_name, type: String, desc: "User's last name"
requires :email, type: String, desc: "User's email name"
requires :password, type: String, desc: "User's password"
optional :date_of_birth, type: String, desc: "User's date of birth / Format: YYYY:MM:DD HH:MM:SS TZ"
end

post do
result = Api::Validators::CreateUser.new(declared(params)).validate
error!({ errors: result.messages } , 422) unless result.success?

user = Api::Models::User.new(result.output)
user.save

Api::WelcomeUserMailer.perform_async(user.id)

present user, with: Api::Entities::User
end

desc "Updates a user"
params do
optional :first_name, type: String, desc: "User's first name"
optional :last_name, type: String, desc: "User's last name"
optional :email, type: String, desc: "User's email name"
optional :date_of_birth, type: String, desc: "User's date of birth / Format: YYYY:MM:DD HH:MM:SS TZ"
end

put '/:id' do
authenticate!

result = Api::Validators::UpdateUser.new(declared(params)).validate
error!({ errors: result.messages } , 422) unless result.success?

unless user = Api::Models::User.where(id: params[:id]).first
return api_response({error_type: :not_found})
end

unless current_user.can?(:edit, user)
return api_response({error_type: :forbidden})
end

user.update(result.output)

present user, with: Api::Entities::User
end

desc "Resets a user's password"
params do
requires :new_password, type: String, desc: "New password"
requires :confirm_password, type: String, desc: "Password confirmation"
end

patch '/:id/reset_password' do
result = Api::Validators::ResetUserPassword.new(declared(params)).validate
error!({ errors: result.messages } , 422) unless result.success?

unless user = Api::Models::User.where(id: params[:id]).first
return api_response({error_type: :not_found})
end

user.password = result.output['new_password']
user.save

Api::ResetPasswordMailer.perform_async(user.id)

api_response({status: :ok})
end

desc "Signin a user"
params do
requires :email, type: String, desc: "User's email"
requires :password, type: String, desc: "User's password"
end

post '/signin' do
result = Api::Validators::UserSignin.new(declared(params)).validate
error!({ errors: result.messages } , 422) unless result.success?

unless user = Api::Models::User.where(email: result.output['email']).first
return api_response({error_type: :unauthorized})
end

unless user.password == result.output['password']
return api_response({error_type: :unauthorized})
end

token = generate_token_for_user(user)
api_response({token: token})
end

end
end
15 changes: 15 additions & 0 deletions application/api_entities/user.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class Api
module Entities
class User < Grape::Entity
root 'users', 'user'

expose :id
expose :full_name
expose :first_name
expose :last_name
expose :email
expose :date_of_birth, format_with: :datetime_string

end
end
end
10 changes: 10 additions & 0 deletions application/api_helpers/api_format.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class Api
module ApiFormat
extend Grape::API::Helpers

Grape::Entity.format_with :datetime_string do |date|
date.to_time.to_s if date
end

end
end
2 changes: 2 additions & 0 deletions application/api_helpers/api_response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ def api_response response
status 404
when :forbidden
status 403
when :unauthorized
status 401
else
status 400
end
Expand Down
15 changes: 14 additions & 1 deletion application/api_helpers/auth.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,25 @@ module Auth

module HelperMethods
def authenticate!
# Library to authenticate user can go here
token = request.env['HTTP_AUTHORIZATION']
payload = JWT.decode(token, HMAC_SECRET)[0]
if @current_user = ::Api::Models::User.where(email: payload['email']).first
true
else
error!({error_type: 'not_authorized'}, 401)
end
rescue JWT::DecodeError, JWT::VerificationError
error!({error_type: 'not_authorized'}, 401)
end

def current_user
@current_user
end

def generate_token_for_user(user)
payload = { email: user.email }
JWT.encode(payload, HMAC_SECRET)
end
end
end
end
16 changes: 16 additions & 0 deletions application/api_validators/create_user.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
class Api
module Validators
class CreateUser
include Hanami::Validations
predicates FormPredicates

validations do
required('first_name') { filled? & str? }
required('last_name') { filled? & str? }
required('email') { email? }
required('password') { filled? & str? }
optional('date_of_birth') { datetime_str? }
end
end
end
end
19 changes: 19 additions & 0 deletions application/api_validators/reset_user_password.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
class Api
module Validators
class ResetUserPassword

include Hanami::Validations
predicates FormPredicates

validations do
required('new_password').filled(:str?)
required('confirm_password').filled(:str?)

rule(confirm_password: ['new_password', 'confirm_password']) do |new_password, confirm_password|
new_password.eql?(confirm_password)
end
end

end
end
end
Loading