Prerequisitie: | |
Ruby 2.0.0-p481 | |
Rails 4.1.6 | |
[Step 1] | |
- rails new wids2015 | |
[Step 2] Add following lines to Gemfile | |
gem 'devise' | |
gem 'rails_admin' | |
gem 'grape', '~> 0.6.1' | |
gem 'grape-entity', '~> 0.4.0' | |
gem 'rack-contrib', '~> 1.1.0' | |
gem 'grape-swagger', '~> 0.7.2' | |
gem 'grape-swagger-rails', '~> 0.0.8' | |
[Step 3] | |
- bundle install | |
- rails g devise:install | |
- rails g devise User | |
- rails g model Watch name:string user:references | |
[Step 4] Edit app/models/user.rb | |
has_many :watches | |
[Step 5] | |
- rake db:migrate | |
- rails g rails_admin:install | |
[Step 6] Uncomment config/initializer/rails_admin.rb | |
config.authenticate_with do | |
warden.authenticate! scope: :user | |
end | |
config.current_user_method(&:current_user) | |
[Step 7] | |
- rails s | |
------------------------------------------------------------------------------ | |
You will be able to work with rails_admin and devise now. | |
http://localhost:3000/admin | |
------------------------------------------------------------------------------ | |
[Step 8] | |
- rails g model Token secret_id:string hashed_secret:string expires_at:datetime user:references | |
[Step 9] add after create table db/migrate/[date]_create_tokens | |
- add_index :tokens, :secret_id, name: "index_tokens_secret_id", unique: true | |
[Step 10] | |
- rake db:migrate | |
[Step 11] Edit app/models/token.rb | |
before_create :generate_secret | |
attr_accessor :secret | |
def expired? | |
DateTime.now >= self.expires_at | |
end | |
def self.find_authenticated secret_id, secret | |
token = where(secret_id: secret_id).first | |
token if token && token.has_secret?(secret) | |
end | |
def has_secret? secret | |
BCrypt::Password.new(self.hashed_secret) == secret | |
end | |
private | |
def generate_secret | |
begin | |
self.secret_id = SecureRandom.hex 8 | |
end while Token.exists?(secret_id: self.secret_id) | |
@secret = SecureRandom.urlsafe_base64 32 | |
self.hashed_secret = BCrypt::Password.create secret, cost: 10 | |
self.expires_at = DateTime.now + 30.days | |
end | |
[Step 12] Edit app/models/user.rb | |
def generate_authentication_token | |
Token.create(user: self) | |
end | |
[Step 13] Add lib/api/entities.rb | |
module API | |
module Entities | |
class EntityToken < Grape::Entity | |
expose :secret_id | |
expose :secret | |
expose :created_at | |
expose :updated_at | |
end | |
end | |
end | |
[Step 14] Add lib/api/entities.rb | |
module API | |
module Entities | |
class EntityWatch < Grape::Entity | |
expose :id | |
expose :name | |
expose :created_at | |
expose :updated_at | |
end | |
end | |
end | |
[Step 15] Add lib/api/v1/users.rb | |
module API | |
module V1 | |
class Users < Grape::API | |
version 'v1' | |
format :json | |
resource :login do | |
desc "Return logged in user with access token" | |
params do | |
requires :email, type: String, desc: "Email" | |
requires :password, type: String, desc: "Password" | |
end | |
get do | |
user = User.find_by_email(params[:email].downcase) | |
if user && user.valid_password?(params[:password]) | |
token = user.generate_authentication_token | |
user.sign_in_count += 1 | |
if user.save | |
present :token, token, with: API::Entities::EntityToken | |
end | |
else | |
error!(I18n.t("devise.failure.invalid", :authentication_keys => Devise.authentication_keys.join(", ")), 403) | |
end | |
end | |
end | |
resource :watches do | |
desc "Return list of watches" | |
params do | |
requires :secret_id, type: String, desc: "Secret id" | |
requires :secret, type: String, desc: "Secret" | |
end | |
get do | |
authenticate! | |
present :watches, @user.watches, with: API::Entities::EntityWatch, size: params[:size] | |
end | |
desc "Add a watch" | |
params do | |
requires :secret_id, type: String, desc: "Secret id" | |
requires :secret, type: String, desc: "Secret" | |
optional :name, type: String, desc: "Name of watch" | |
end | |
get 'add' do | |
authenticate! | |
watch = Watch.create!(name: params[:name], user: @user) | |
watch.save! | |
present :watch, watch, with: API::Entities::EntityWatch, size: params[:size] | |
end | |
end | |
end | |
end | |
end | |
[Step 16] Add lib/v1/root.rb | |
module API | |
module V1 | |
class Root < Grape::API | |
mount API::V1::Users | |
end | |
end | |
end | |
[Step 17] Add lib/api/root.rb | |
require 'grape-swagger' | |
module API | |
class Root < Grape::API | |
helpers do | |
def authenticate! | |
error!({message: I18n.t("devise.errors.messages.invalid_token"), code: -90001}, 403) unless current_user | |
end | |
def current_user | |
@user || find_user_by_secret | |
end | |
private | |
def find_user_by_secret | |
secret_id = params[:secret_id].presence | |
secret = params[:secret].presence | |
token = secret_id && secret && Token.find_authenticated(secret_id, secret) | |
@user = token.user if token | |
return @user | |
end | |
end | |
mount API::V1::Root | |
end | |
end | |
[Step 18] Edit config/locales/devise.en.yml for devise.errors.messages.invalid_token | |
invalid_token: "Invalid Token." | |
[Step 19] Edit config/initializer/rails_admin.rb | |
Rails.application.eager_load! | |
config.included_models = ActiveRecord::Base.descendants.map!(&:name) | |
[Step 20] Edit config/application.rb | |
config.autoload_paths += %W(#{config.root}/lib) | |
config.autoload_paths += Dir["#{config.root}/lib/**/"] | |
[Step 21] Edit config/routes.rb | |
mount API::Root => '/api', as: 'api' | |
------------------------------------------------------------------------------ | |
You will be able to access the api now. | |
http://localhost:3000/api/v1/login.json?email=[your email]&password=[your password] | |
------------------------------------------------------------------------------ | |
[Step 22] Add config/initializer/grape_swagger_rails.rb | |
require 'grape-swagger-rails' | |
GrapeSwaggerRails.options.url = "/api/swagger_doc.json" | |
GrapeSwaggerRails.options.app_name = 'wids2015' | |
GrapeSwaggerRails.options.app_url = '/' | |
[Step 23] Edit config/routes.rb | |
mount GrapeSwaggerRails::Engine => '/apidoc' | |
[Step 24] Edit lib/api/root.rb | |
add_swagger_documentation( | |
base_path: "/api", | |
api_version: "v1", | |
hide_documentation_path: true | |
) | |
------------------------------------------------------------------------------ | |
You will be able to access the api doc now. | |
http://localhost:3000/apidoc | |
------------------------------------------------------------------------------ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment