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
29 changes: 15 additions & 14 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
source 'https://rubygems.org'

git_source(:github) do |repo_name|
repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?('/')
"https://github.com/#{repo_name}.git"
end

Expand All @@ -22,28 +22,29 @@ gem 'grape-entity', '~> 0.6.1'
gem 'grape-swagger', '~> 0.27.3'
gem 'grape-swagger-entity', '~> 0.2.1'
gem 'jwt', '~> 2.0.0'
gem 'hashie-forbidden_attributes'

# Codecoverage
gem 'coveralls', require: false

group :development, :test do
gem 'dotenv-rails', '~> 2.2.1'

gem 'byebug', platforms: %i[mri mingw x64_mingw]
gem 'pry-byebug'
gem 'byebug', platforms: %i[mri mingw x64_mingw]
gem 'pry-byebug'

# Testing
gem 'factory_bot_rails', '~> 4.0'
gem 'json_matchers', '~> 0.7.0'
gem 'rspec-rails', '~> 3.6.0'
gem 'rspec_junit_formatter', '~> 0.3.0', require: false
gem 'webmock', '~> 3.0.1', require: false
# Testing
gem 'factory_bot_rails', '~> 4.0'
gem 'json_matchers', '~> 0.7.0'
gem 'rspec-rails', '~> 3.6.0'
gem 'rspec_junit_formatter', '~> 0.3.0', require: false
gem 'webmock', '~> 3.0.1', require: false

gem 'rubocop', '~> 0.49.1', require: false
gem 'rubocop-checkstyle_formatter', '~> 0.4.0', require: false
gem 'rubocop-rspec', '~> 1.16.0', require: false
gem 'rubocop', '~> 0.49.1', require: false
gem 'rubocop-checkstyle_formatter', '~> 0.4.0', require: false
gem 'rubocop-rspec', '~> 1.16.0', require: false

gem 'simplecov', '~> 0.15.1', require: false
gem 'simplecov', '~> 0.15.1', require: false
end

group :development do
Expand All @@ -55,4 +56,4 @@ group :development do
end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby]
4 changes: 4 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ GEM
grape-entity (>= 0.5.0)
grape-swagger (>= 0.20.4)
hashdiff (0.3.7)
hashie (3.5.6)
hashie-forbidden_attributes (0.1.1)
hashie (>= 3.0)
http-cookie (1.0.3)
domain_name (~> 0.5)
i18n (0.9.0)
Expand Down Expand Up @@ -272,6 +275,7 @@ DEPENDENCIES
grape-entity (~> 0.6.1)
grape-swagger (~> 0.27.3)
grape-swagger-entity (~> 0.2.1)
hashie-forbidden_attributes
json_matchers (~> 0.7.0)
jwt (~> 2.0.0)
listen (>= 3.0.5, < 3.2)
Expand Down
10 changes: 10 additions & 0 deletions app/api/api/api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@ module API
class API < Grape::API
format :json

HEADERS = {
Authorization: {
description: 'JWT - Validates your identity',
required: true
}
}.freeze

helpers API::AuthHelper

get '/' do
{ version: `git rev-parse --short HEAD`.strip }
end
Expand All @@ -10,6 +19,7 @@ class API < Grape::API
mount API::Locations
mount API::Events
mount API::SignUp
mount API::Auth

add_swagger_documentation(
mount_path: '/swagger_doc',
Expand Down
17 changes: 17 additions & 0 deletions app/api/api/auth.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module API
class Auth < Grape::API
resource :auth do
desc 'Authorize user and return JWT token' do
end
params do
requires :email, type: String, desc: 'Email adress of user', allow_blank: false
requires :password, type: String, desc: 'Password of the user', allow_blank: false
end
post '/session' do
token = AuthorizationService.perform(params[:email], params[:password])
error!("['401'] Unauthorized user might not exists", :unauthorized) unless token
{ token: token }
end
end
end
end
17 changes: 17 additions & 0 deletions app/api/api/auth_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module API
module AuthHelper
def current_user
@current_user ||= User.find_by(id: decoded_token.user_id)
end

private

def decoded_token
@decoded_token ||= OpenStruct.new(TokenProvider.decode(token).first)
end

def token
headers['Authorization']
end
end
end
25 changes: 22 additions & 3 deletions app/api/api/locations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,21 @@ class Locations < Grape::API
get '/' do
present Location.all, with: Entities::Location
end

desc 'Creates a single location' do
success Entities::Location
headers API::HEADERS
end
params do
requires :name, type: String, desc: 'Name for the location'
optional :description, type: String, desc: 'Description for the location'
requires :latitude, type: Float, desc: 'Latitude for the location'
requires :longitude, type: Float, desc: 'Latitude for the location'
requires :latitude, type: Float, desc: 'Latitude for the location', values: -90.0..+90.0
requires :longitude, type: Float, desc: 'Latitude for the location', values: -180.0..+180.0
requires :image_url, type: String, desc: 'Path to image for the location'
end
post '/' do
location = current_user.locations.create(params)
error!("['422'] Unprocessable entity", 422) unless location.persisted?
present location, with: Entities::Location
end
desc 'Returns a single location for given id' do
success Entities::Location
Expand All @@ -33,6 +34,24 @@ class Locations < Grape::API
error!("['404'] Resource not found", 404) unless location
present location, with: Entities::Location
end
desc 'Updates a given location' do
success Entities::Location
headers API::HEADERS
end
params do
requires :name, type: String, desc: 'Name for the location'
optional :description, type: String, desc: 'Description for the location'
requires :latitude, type: Float, desc: 'Latitude for the location', values: -90.0..+90.0
requires :longitude, type: Float, desc: 'Latitude for the location', values: -180.0..+180.0
requires :image_url, type: String, desc: 'Path to image for the location'
end
put '/:id' do
location = current_user.locations.find_by(id: params[:id])
error!("['404'] Resource not found", 404) unless location
location.update_attributes(params)
error!("['422'] Unprocessable entity", 422) unless location.valid?
present location, with: Entities::Location
end
end
end
end
2 changes: 1 addition & 1 deletion app/api/api/sign_up.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class SignUp < Grape::API
post '/' do
user = User.find_by(email: params[:email])
error!('[400] User with email already exists', 400) if user
User.create(params)
user = User.create(params)
token = TokenProvider.issue_token(user)
{ token: token }
end
Expand Down
27 changes: 27 additions & 0 deletions app/services/authorization_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
class AuthorizationService
def self.perform(email, password)
new(email, password).perform
end

def initialize(email, password)
@email = email
@password = password
end

def perform
return unless authorize?
TokenProvider.issue_token(user)
end

private

attr_reader :email, :password

def user
@user ||= User.find_by(email: email)
end

def authorize?
user && user.authenticate(password)
end
end
22 changes: 14 additions & 8 deletions app/services/token_provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,24 @@ class TokenProvider
def self.issue_token(user)
payload = {
user_id: user.id,
user_email: user.email,
exp: 1.day.from_now.to_i
}
JWT.encode payload, SECRET, 'HS256'
end

def self.valid?(token)
begin
JWT.decode token, SECRET, true, algorithm: 'HS256'
rescue JWT::DecodeError, JWT::ImmatureSignature
# IF this token is invalid, the JWT gem will raise this error and by caching it in this block
# the returning valud should be false
false
end
JWT.decode token, SECRET, true, algorithm: 'HS256'
rescue JWT::DecodeError, JWT::ImmatureSignature
# IF this token is invalid, the JWT gem will raise this error and by
# catching it in this block
# the returning valud should be false
false
end
end

def self.decode(token)
JWT.decode token, SECRET, true, algorithm: 'HS256'
rescue JWT::DecodeError, JWT::ImmatureSignature
{}
end
end
5 changes: 5 additions & 0 deletions db/migrate/20171029162844_change_column_default.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class ChangeColumnDefault < ActiveRecord::Migration[5.1]
def change
change_column_default :events, :archived, false
end
end
8 changes: 8 additions & 0 deletions db/migrate/20171029163211_not_null_latitude_longitude.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class NotNullLatitudeLongitude < ActiveRecord::Migration[5.1]
def change
change_column_null(:locations, :latitude, false)
change_column_null(:locations, :longitude, false)
change_column_null(:locations, :name, false)
change_column_default(:locations, :archived, false)
end
end
7 changes: 7 additions & 0 deletions db/migrate/20171029163428_not_null_events.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class NotNullEvents < ActiveRecord::Migration[5.1]
def change
change_column_null(:events, :name, false)
change_column_null(:events, :start_time, false)
change_column_null(:events, :archived, false)
end
end
16 changes: 8 additions & 8 deletions db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,20 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 20171028191027) do
ActiveRecord::Schema.define(version: 20171029163428) do

# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"

create_table "events", force: :cascade do |t|
t.string "name"
t.string "name", null: false
t.string "description"
t.datetime "start_time"
t.datetime "start_time", null: false
t.string "sound_cloud_url"
t.string "sound_cloud_user_id"
t.bigint "location_id"
t.bigint "user_id"
t.boolean "archived"
t.boolean "archived", default: false, null: false
t.string "image_url"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
Expand All @@ -32,12 +32,12 @@
end

create_table "locations", force: :cascade do |t|
t.string "name"
t.string "name", null: false
t.string "description"
t.float "latitude"
t.float "longitude"
t.float "latitude", null: false
t.float "longitude", null: false
t.bigint "user_id"
t.boolean "archived"
t.boolean "archived", default: false
t.string "image_url"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
Expand Down
52 changes: 52 additions & 0 deletions spec/api/api/auth_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
require 'rails_helper'

describe API::Auth do
let(:user) { create :user }
let(:params) do
{
email: user.email,
password: user.password
}
end

context 'User exists and email and password are correct' do
before do
post '/auth/session', params: params, headers: nil
end
it 'returns a with succes http status' do
expect(response).to have_http_status(:success)
end

it 'matches schema' do
expect(response).to match_response_schema('token')
end
end

context 'User exists but password is incorrect' do
let(:params) do
{
email: user.email,
password: user.password + ' wrong'
}
end

it 'returns unauthorized' do
post '/auth/session', params: params, headers: nil
expect(response).to have_http_status(:unauthorized)
end
end

context 'User does not exists' do
let(:params) do
{
email: 'Do not exist',
password: ' wrong'
}
end

it 'returns unauthorized' do
post '/auth/session', params: params, headers: nil
expect(response).to have_http_status(:unauthorized)
end
end
end
Loading