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
1 change: 1 addition & 0 deletions .env.development.sample
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ 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=57f1bcf21caed1930fba8ac4ef74b1636d80bb9347b5af3863e8897fe10a98eded469734909903c5a3c166fec8d536b81b3636eda644c5c6a1a79b83a193d59e
1 change: 1 addition & 0 deletions .env.test.sample
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ 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=a53d4a36f4bf1e08e14a0cbd24401856aaeac17f96311d199f51c90c100bfb4f175e679017dd9125b903cc97aa0561e7b533154f76681df00bd97336ec6c9edc
3 changes: 3 additions & 0 deletions .rspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
--color
--require spec_helper
-I ./application/spec
10 changes: 6 additions & 4 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
ruby '2.3.3'
ruby '2.3.4'

source 'https://rubygems.org'

Expand All @@ -14,22 +14,24 @@ gem 'grape-swagger-entity', '0.1.5' # parse entities in api
gem 'rack-indifferent', '1.1' # makes param keys symbols
gem 'mysql2', '0.4.5'
gem 'sequel', '4.40.0'
gem 'sequel_secure_password'
gem 'mail', '2.6.4'
gem 'uuidtools', ' 2.1.5'
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 'sucker_punch'
gem 'jwt'

group :development, :test do
gem 'awesome_print', '1.7.0'
gem 'pry', '0.10.4'
gem 'pry-doc'
gem 'pry-byebug'
end

group :test do
gem 'webmock', '2.1.0'
gem 'vcr', '3.0.3'
gem 'database_cleaner', '1.5.3'
gem 'factory_girl', '4.7.0'
gem 'faker', '1.6.6'
gem 'rack-test', '0.6.3'
Expand Down
38 changes: 21 additions & 17 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,19 @@ GEM
i18n (~> 0.7)
minitest (~> 5.1)
tzinfo (~> 1.1)
addressable (2.5.0)
public_suffix (~> 2.0, >= 2.0.2)
awesome_print (1.7.0)
axiom-types (0.1.1)
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)
byebug (9.0.6)
coderay (1.1.1)
coercible (1.0.0)
descendants_tracker (~> 0.0.1)
concurrent-ruby (1.0.4)
crack (0.4.3)
safe_yaml (~> 1.0.0)
daemons (1.2.4)
database_cleaner (1.5.3)
descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1)
diff-lcs (1.2.5)
Expand All @@ -31,7 +28,7 @@ GEM
dry-container (0.6.0)
concurrent-ruby (~> 1.0)
dry-configurable (~> 0.1, >= 0.1.3)
dry-core (0.2.1)
dry-core (0.2.3)
concurrent-ruby (~> 1.0)
dry-equalizer (0.2.0)
dry-logic (0.4.0)
Expand Down Expand Up @@ -85,11 +82,11 @@ GEM
hanami-validations (0.6.0)
dry-validation (~> 0.9)
hanami-utils (~> 0.8)
hashdiff (0.3.1)
hashie (3.4.6)
i18n (0.7.0)
ice_nine (0.11.2)
inflecto (0.0.2)
jwt (1.5.6)
listen (3.1.5)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
Expand All @@ -112,7 +109,12 @@ GEM
coderay (~> 1.1.0)
method_source (~> 0.8.1)
slop (~> 3.4)
public_suffix (2.0.4)
pry-byebug (3.4.2)
byebug (~> 9.0)
pry (~> 0.10)
pry-doc (0.9.0)
pry (~> 0.9)
yard (~> 0.8)
rack (1.6.4)
rack-accept (0.4.5)
rack (>= 0.4)
Expand Down Expand Up @@ -140,9 +142,13 @@ GEM
rspec-support (~> 3.5.0)
rspec-support (3.5.0)
ruby_dep (1.5.0)
safe_yaml (1.0.4)
sequel (4.40.0)
sequel_secure_password (0.2.12)
bcrypt (>= 3.1, < 4.0)
sequel (>= 4.1.0, < 5.0)
slop (3.6.0)
sucker_punch (2.0.2)
concurrent-ruby (~> 1.0.0)
thin (1.7.0)
daemons (~> 1.0, >= 1.0.9)
eventmachine (~> 1.0, >= 1.0.4)
Expand All @@ -152,16 +158,12 @@ GEM
tzinfo (1.2.2)
thread_safe (~> 0.1)
uuidtools (2.1.5)
vcr (3.0.3)
virtus (1.0.5)
axiom-types (~> 0.1)
coercible (~> 1.0)
descendants_tracker (~> 0.0, >= 0.0.3)
equalizer (~> 0.0, >= 0.0.9)
webmock (2.1.0)
addressable (>= 2.3.6)
crack (>= 0.3.2)
hashdiff
yard (0.9.5)

PLATFORMS
ruby
Expand All @@ -170,7 +172,6 @@ DEPENDENCIES
ability_list (= 0.0.4)
activesupport (= 5.0.0)
awesome_print (= 1.7.0)
database_cleaner (= 1.5.3)
dry-validation (= 0.10.4)
factory_girl (= 4.7.0)
faker (= 1.6.6)
Expand All @@ -180,20 +181,23 @@ DEPENDENCIES
grape-swagger (= 0.25.1)
grape-swagger-entity (= 0.1.5)
hanami-validations (= 0.6.0)
jwt
mail (= 2.6.4)
mysql2 (= 0.4.5)
pry (= 0.10.4)
pry-byebug
pry-doc
rack (= 1.6.4)
rack-indifferent (= 1.1)
rack-test (= 0.6.3)
rake (= 11.2.2)
rerun (= 0.11.0)
rspec (= 3.5.0)
sequel (= 4.40.0)
sequel_secure_password
sucker_punch
thin (= 1.7.0)
uuidtools (= 2.1.5)
vcr (= 3.0.3)
webmock (= 2.1.0)

RUBY VERSION
ruby 2.3.3p222
Expand Down
37 changes: 33 additions & 4 deletions application/api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,32 +15,48 @@
require 'rack/indifferent'
require 'grape'
require 'grape/batch'
require 'sucker_punch'
require 'mail'
require 'jwt'
# Initialize the application so we can add all our components to it
class Api < Grape::API; end

# Include all config files
require 'config/sequel'
require 'config/hanami'
require 'config/grape'
require 'config/mail'

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

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

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

Dir['./application/models/*.rb'].each { |rb| require rb }
Dir['./application/entities/*.rb'].each { |rb| require rb }
Dir['./application/jobs/*.rb'].each { |rb| require rb }
Dir['./application/validators/*.rb'].each { |rb| require rb }
Dir['./application/operations/*.rb'].each { |rb| require rb }
Dir['./application/api_helpers/**/*.rb'].each { |rb| require rb }

class Api < Grape::API
version 'v1.0', using: :path
content_type :json, 'application/json'
content_type :txt, 'text/plain'
default_format :json
prefix :api

logger PrettyLogger.logger

rescue_from Grape::Exceptions::ValidationErrors do |e|
ret = { error_type: 'validation', errors: {} }
e.each do |x, err|
Expand All @@ -50,17 +66,30 @@ class Api < Grape::API
error! ret, 400
end

rescue_from Sequel::NoMatchingRow do |e|
error!({ error_type: 'not_found' }, 404)
end

rescue_from :all do |e|
Api.logger.error(e.class)
Api.logger.error(e.message)
Api.logger.error(e.backtrace.join("\n"))
error!({ error_type: 'internal' }, 500)
end

helpers SharedParams
helpers ApiResponse
include Auth

before do
header['Access-Control-Allow-Origin'] = '*'
header['Access-Control-Request-Method'] = '*'

authenticate!
end

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

add_swagger_documentation \
mount_path: '/docs'
add_swagger_documentation mount_path: '/docs'
end
23 changes: 23 additions & 0 deletions application/api/auth.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
class Api
namespace :auth do

desc 'Generates a new authentication token',
entity: Models::Login::Authorization,
params: Models::Login::Input.documentation_in_body,
failure: [ { code: 403, message: 'Unauthorized' } ]
post 'login' do
Login.(params) do
ok { |user| { token: auth_token_for(user) } }
fail { |errors| api_response errors }
end
end

desc 'Generates a new reset password code',
success: { code: 204 }
post 'reset_password_code/:user_id' do
NewResetPasswordCode.(params)
body false
end

end
end
44 changes: 40 additions & 4 deletions application/api/users.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,47 @@ class Api
params do
includes :basic_search
end

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


desc 'Creates a new user',
entity: Models::User::Entity,
params: Models::User::Input.documentation_in_body,
failure: [ { code: 422, message: 'Invalid input' } ]
post do
CreateUser.(params) do
ok { |user| present user }
fail { |errors| api_response errors }
end
end

route_param :id do
desc "Resets a user's password",
params: Models::PasswordReset::Input.documentation_in_body,
success: { code: 204 },
failure: [ { code: 422, message: 'Invalid input' }, { code: 401, message: 'Invalid verification code' } ]
patch :reset_password do
ResetPassword.(params) do
ok { body false }
fail { |errors| api_response errors }
end
end

desc 'Updates an existing user',
entity: Models::User::Entity,
params: Models::User::Input.documentation_in_body,
failure: [ { code: 422, message: 'Invalid input' }, { code: 403, message: 'Unauthorized operation attempt' } ],
headers: { 'Authorization' => { description: 'JWT Authorization Token', required: true } }
put do
UpdateUser.(current_user, params) do
ok { |user| present user }
fail { |errors| api_response errors }
end
end
end
end
end
4 changes: 4 additions & 0 deletions application/api_helpers/api_response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ def api_response response
status 404
when :forbidden
status 403
when :unauthorized
status 401
when :unprocessable_entity, :invalid
status 422
else
status 400
end
Expand Down
20 changes: 19 additions & 1 deletion application/api_helpers/auth.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,30 @@ module Auth

module HelperMethods
def authenticate!
# Library to authenticate user can go here
if token = headers['Authorization']
@current_user = Models::User.with_pk(extract_user_id(token))
end
rescue JWT::DecodeError
error!({error_type: :unauthorized}, :unauthorized)
end

def current_user
@current_user
end

def extract_user_id(token)
JWT.decode(token, HMAC_SECRET, true, algorithm: 'HS256')[0]['user_id']
end

def auth_token_for(user)
payload = {
iss: "ruby-api-example",
exp: Time.now.to_i + 4.hours,
user_id: user.id
}

JWT.encode(payload, HMAC_SECRET, 'HS256')
end
end
end
end
6 changes: 6 additions & 0 deletions application/config/grape.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,9 @@
config.logger = Logger.new(STDOUT)
config.session_proc = Proc.new { }
end

class Grape::Entity
def self.documentation_in_body
documentation.transform_values { |v| v.merge(in: 'body') }
end
end
2 changes: 1 addition & 1 deletion application/config/hanami.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ module FormPredicates
predicate(:datetime_str?) do |current|
# Format: YYYY-MM-DD HH:MM:SS TZ - ex: 2016-01-01 02:03:04 -0800
# Timezone is optional
current.match(/^\d{4}-\d{2}-\d{2} \d{1,2}\:\d{1,2}\:\d{1,2}( \-\d{4})?$/)
current.match(/^\d{4}-\d{2}-\d{2} \d{1,2}\:\d{1,2}\:\d{1,2}( [+-]\d{4})?$/)
end
end
Loading