Commit d1d8cd9866cfd5930b46a19cf60f48be44dc162c
1 parent
b0658885
Exists in
staging
and in
4 other branches
adding authentication and entity mananegement
Showing
10 changed files
with
302 additions
and
37 deletions
Show diff stats
app/models/article.rb
| @@ -790,12 +790,6 @@ class Article < ActiveRecord::Base | @@ -790,12 +790,6 @@ class Article < ActiveRecord::Base | ||
| 790 | true | 790 | true |
| 791 | end | 791 | end |
| 792 | 792 | ||
| 793 | - #FIXME make this test | ||
| 794 | - #Define which parameters will be returned in json object | ||
| 795 | - def as_json(options = {}) | ||
| 796 | - super(:only => [:id, :name, :body, :created_at], :methods => [:title]) | ||
| 797 | - end | ||
| 798 | - | ||
| 799 | private | 793 | private |
| 800 | 794 | ||
| 801 | def sanitize_tag_list | 795 | def sanitize_tag_list |
app/models/user.rb
| 1 | require 'digest/sha1' | 1 | require 'digest/sha1' |
| 2 | require 'user_activation_job' | 2 | require 'user_activation_job' |
| 3 | +require 'securerandom' | ||
| 3 | 4 | ||
| 4 | # User models the system users, and is generated by the acts_as_authenticated | 5 | # User models the system users, and is generated by the acts_as_authenticated |
| 5 | # Rails generator. | 6 | # Rails generator. |
| @@ -119,6 +120,18 @@ class User < ActiveRecord::Base | @@ -119,6 +120,18 @@ class User < ActiveRecord::Base | ||
| 119 | self.update_attribute :last_login_at, Time.now | 120 | self.update_attribute :last_login_at, Time.now |
| 120 | end | 121 | end |
| 121 | 122 | ||
| 123 | + #FIXME make this test | ||
| 124 | + def generate_private_token! | ||
| 125 | + self.private_token = SecureRandom.hex | ||
| 126 | + self.private_token_generated_at = DateTime.now | ||
| 127 | + save(false) | ||
| 128 | + end | ||
| 129 | + | ||
| 130 | + #FIXME make this test | ||
| 131 | + def private_token_expired? | ||
| 132 | + self.generate_private_token! if self.private_token.nil? || (self.private_token_generated_at + 2.weeks < DateTime.now) | ||
| 133 | + end | ||
| 134 | + | ||
| 122 | # Activates the user in the database. | 135 | # Activates the user in the database. |
| 123 | def activate | 136 | def activate |
| 124 | return false unless self.person | 137 | return false unless self.person |
db/migrate/20140407013817_add_private_token_info_to_users.rb
| 1 | class AddPrivateTokenInfoToUsers < ActiveRecord::Migration | 1 | class AddPrivateTokenInfoToUsers < ActiveRecord::Migration |
| 2 | def self.up | 2 | def self.up |
| 3 | - add_column :users, :private_token | 3 | + add_column :users, :private_token, :string |
| 4 | add_column :users, :private_token_generated_at, :datetime | 4 | add_column :users, :private_token_generated_at, :datetime |
| 5 | end | 5 | end |
| 6 | 6 |
lib/api/api.rb
| 1 | require 'grape' | 1 | require 'grape' |
| 2 | +Dir["#{Rails.root}/lib/api/*.rb"].each {|file| require file} | ||
| 2 | 3 | ||
| 3 | module API | 4 | module API |
| 4 | class API < Grape::API | 5 | class API < Grape::API |
| @@ -9,10 +10,10 @@ module API | @@ -9,10 +10,10 @@ module API | ||
| 9 | 10 | ||
| 10 | mount V1::Articles | 11 | mount V1::Articles |
| 11 | mount V1::Comments | 12 | mount V1::Comments |
| 13 | + mount V1::Users | ||
| 14 | + mount Session | ||
| 12 | 15 | ||
| 13 | -# helpers APIHelpers | ||
| 14 | - | ||
| 15 | -# require Articles | 16 | + helpers APIHelpers |
| 16 | 17 | ||
| 17 | end | 18 | end |
| 18 | end | 19 | end |
| @@ -0,0 +1,27 @@ | @@ -0,0 +1,27 @@ | ||
| 1 | +module API | ||
| 2 | + module Entities | ||
| 3 | + class Article < Grape::Entity | ||
| 4 | + expose :id, :name, :body, :created_at | ||
| 5 | +# expose :is_admin?, as: :is_admin | ||
| 6 | + | ||
| 7 | +# expose :avatar_url do |user, options| | ||
| 8 | +# if user.avatar.present? | ||
| 9 | +# user.avatar.url | ||
| 10 | +# end | ||
| 11 | +# end | ||
| 12 | + end | ||
| 13 | + | ||
| 14 | + class Comment < Grape::Entity | ||
| 15 | + expose :author_id, :body, :title, :created_at | ||
| 16 | + end | ||
| 17 | + | ||
| 18 | + class User < Grape::Entity | ||
| 19 | + expose :login | ||
| 20 | + end | ||
| 21 | + | ||
| 22 | + class UserLogin < User | ||
| 23 | + expose :private_token | ||
| 24 | + end | ||
| 25 | + | ||
| 26 | + end | ||
| 27 | +end |
| @@ -0,0 +1,108 @@ | @@ -0,0 +1,108 @@ | ||
| 1 | +module API | ||
| 2 | + module APIHelpers | ||
| 3 | + PRIVATE_TOKEN_PARAM = :private_token | ||
| 4 | + | ||
| 5 | + def current_user | ||
| 6 | + private_token = params[PRIVATE_TOKEN_PARAM].to_s | ||
| 7 | + @current_user ||= User.find_by_private_token(private_token) | ||
| 8 | + @current_user = nil if !@current_user.nil? && @current_user.private_token_expired? | ||
| 9 | + @current_user | ||
| 10 | + end | ||
| 11 | + | ||
| 12 | + def logout | ||
| 13 | + @current_user = nil | ||
| 14 | + end | ||
| 15 | + | ||
| 16 | + | ||
| 17 | +# def paginate(relation) | ||
| 18 | +# per_page = params[:per_page].to_i | ||
| 19 | +# paginated = relation.page(params[:page]).per(per_page) | ||
| 20 | +# add_pagination_headers(paginated, per_page) | ||
| 21 | +# | ||
| 22 | +# paginated | ||
| 23 | +# end | ||
| 24 | + | ||
| 25 | + def authenticate! | ||
| 26 | + unauthorized! unless current_user | ||
| 27 | + end | ||
| 28 | + | ||
| 29 | +# def authenticated_as_admin! | ||
| 30 | +# forbidden! unless current_user.is_admin? | ||
| 31 | +# end | ||
| 32 | +# | ||
| 33 | +# def authorize! action, subject | ||
| 34 | +# unless abilities.allowed?(current_user, action, subject) | ||
| 35 | +# forbidden! | ||
| 36 | +# end | ||
| 37 | +# end | ||
| 38 | +# | ||
| 39 | +# def can?(object, action, subject) | ||
| 40 | +# abilities.allowed?(object, action, subject) | ||
| 41 | +# end | ||
| 42 | + | ||
| 43 | + # Checks the occurrences of required attributes, each attribute must be present in the params hash | ||
| 44 | + # or a Bad Request error is invoked. | ||
| 45 | + # | ||
| 46 | + # Parameters: | ||
| 47 | + # keys (required) - A hash consisting of keys that must be present | ||
| 48 | + def required_attributes!(keys) | ||
| 49 | + keys.each do |key| | ||
| 50 | + bad_request!(key) unless params[key].present? | ||
| 51 | + end | ||
| 52 | + end | ||
| 53 | + | ||
| 54 | + def attributes_for_keys(keys) | ||
| 55 | + attrs = {} | ||
| 56 | + keys.each do |key| | ||
| 57 | + attrs[key] = params[key] if params[key].present? or (params.has_key?(key) and params[key] == false) | ||
| 58 | + end | ||
| 59 | + attrs | ||
| 60 | + end | ||
| 61 | + | ||
| 62 | + # error helpers | ||
| 63 | + | ||
| 64 | + def forbidden! | ||
| 65 | + render_api_error!('403 Forbidden', 403) | ||
| 66 | + end | ||
| 67 | + | ||
| 68 | + def bad_request!(attribute) | ||
| 69 | + message = ["400 (Bad request)"] | ||
| 70 | + message << "\"" + attribute.to_s + "\" not given" | ||
| 71 | + render_api_error!(message.join(' '), 400) | ||
| 72 | + end | ||
| 73 | + | ||
| 74 | + def not_found!(resource = nil) | ||
| 75 | + message = ["404"] | ||
| 76 | + message << resource if resource | ||
| 77 | + message << "Not Found" | ||
| 78 | + render_api_error!(message.join(' '), 404) | ||
| 79 | + end | ||
| 80 | + | ||
| 81 | + def unauthorized! | ||
| 82 | + render_api_error!('401 Unauthorized', 401) | ||
| 83 | + end | ||
| 84 | + | ||
| 85 | + def not_allowed! | ||
| 86 | + render_api_error!('Method Not Allowed', 405) | ||
| 87 | + end | ||
| 88 | + | ||
| 89 | + def render_api_error!(message, status) | ||
| 90 | + error!({'message' => message}, status) | ||
| 91 | + end | ||
| 92 | + | ||
| 93 | +# private | ||
| 94 | +# | ||
| 95 | +# def add_pagination_headers(paginated, per_page) | ||
| 96 | +# request_url = request.url.split('?').first | ||
| 97 | +# | ||
| 98 | +# links = [] | ||
| 99 | +# links << %(<#{request_url}?page=#{paginated.current_page - 1}&per_page=#{per_page}>; rel="prev") unless paginated.first_page? | ||
| 100 | +# links << %(<#{request_url}?page=#{paginated.current_page + 1}&per_page=#{per_page}>; rel="next") unless paginated.last_page? | ||
| 101 | +# links << %(<#{request_url}?page=1&per_page=#{per_page}>; rel="first") | ||
| 102 | +# links << %(<#{request_url}?page=#{paginated.total_pages}&per_page=#{per_page}>; rel="last") | ||
| 103 | +# | ||
| 104 | +# header 'Link', links.join(', ') | ||
| 105 | +# end | ||
| 106 | + | ||
| 107 | + end | ||
| 108 | +end |
| @@ -0,0 +1,60 @@ | @@ -0,0 +1,60 @@ | ||
| 1 | +module API | ||
| 2 | + | ||
| 3 | +# require 'api/validations/uniqueness' | ||
| 4 | + | ||
| 5 | + # Users API | ||
| 6 | + class Session < Grape::API | ||
| 7 | +#params do | ||
| 8 | +# requires :login, :uniqueness => true | ||
| 9 | +#end | ||
| 10 | + | ||
| 11 | + # Login to get token | ||
| 12 | + # | ||
| 13 | + # Parameters: | ||
| 14 | + # login (*required) - user login or email | ||
| 15 | + # password (required) - user password | ||
| 16 | + # | ||
| 17 | + # Example Request: | ||
| 18 | + # POST /session | ||
| 19 | + get "/login" do | ||
| 20 | +# post "/session" do | ||
| 21 | +environment = nil #FIXME load the correct environment create a method in helper | ||
| 22 | + user ||= User.authenticate(params[:login], params[:password], environment) | ||
| 23 | + | ||
| 24 | + return unauthorized! unless user | ||
| 25 | + user.generate_private_token! | ||
| 26 | + present user, :with => Entities::UserLogin | ||
| 27 | + end | ||
| 28 | + | ||
| 29 | + # Create user. | ||
| 30 | + # | ||
| 31 | + # Parameters: | ||
| 32 | + # email (required) - Email | ||
| 33 | + # password (required) - Password | ||
| 34 | + # name - Name | ||
| 35 | + # Example Request: | ||
| 36 | + # POST /users | ||
| 37 | +# post do | ||
| 38 | + get "register" do | ||
| 39 | + required_attributes! [:email, :login, :password] | ||
| 40 | + attrs = attributes_for_keys [:email, :login, :password] | ||
| 41 | + attrs[:password_confirmation] = attrs[:password] | ||
| 42 | + user = User.new(attrs) | ||
| 43 | +begin | ||
| 44 | + if user.save | ||
| 45 | + present user, :with => Entities::User | ||
| 46 | + else | ||
| 47 | + not_found! | ||
| 48 | + end | ||
| 49 | +rescue | ||
| 50 | +# not_found! | ||
| 51 | +#FIXME See why notfound is not working | ||
| 52 | +{} | ||
| 53 | +end | ||
| 54 | +# user | ||
| 55 | + end | ||
| 56 | + | ||
| 57 | + | ||
| 58 | + | ||
| 59 | + end | ||
| 60 | +end |
lib/api/v1/articles.rb
| 1 | module API | 1 | module API |
| 2 | module V1 | 2 | module V1 |
| 3 | - class Articles < Grape::API | ||
| 4 | - | ||
| 5 | - resource :articles do | 3 | + class Articles < Grape::API |
| 6 | 4 | ||
| 7 | - get do | ||
| 8 | - first_update = DateTime.parse(params[:first_update]) if params[:first_update] | ||
| 9 | - last_update = DateTime.parse(params[:last_update]) if params[:last_update] | 5 | + before { authenticate! } |
| 6 | + | ||
| 7 | + resource :articles do | ||
| 10 | 8 | ||
| 11 | - if first_update.nil? | ||
| 12 | - begin_date = Article.first.created_at | ||
| 13 | - end_date = last_update.nil? ? DateTime.now : last_update | ||
| 14 | - else | ||
| 15 | - begin_date = first_update | ||
| 16 | - end_date = DateTime.now | 9 | + #FIXME See if it's possible to use pagination instead of DateTime control. see a way to use this pagination logic genericaly |
| 10 | + get do | ||
| 11 | + first_update = DateTime.parse(params[:first_update]) if params[:first_update] | ||
| 12 | + last_update = DateTime.parse(params[:last_update]) if params[:last_update] | ||
| 13 | + | ||
| 14 | + if first_update.nil? | ||
| 15 | + begin_date = Article.first.created_at | ||
| 16 | + end_date = last_update.nil? ? DateTime.now : last_update | ||
| 17 | + else | ||
| 18 | + begin_date = first_update | ||
| 19 | + end_date = DateTime.now | ||
| 20 | + end | ||
| 21 | + | ||
| 22 | + limit = params[:limit].to_i | ||
| 23 | + limit = 20 if limit == 0 | ||
| 24 | + conditions = {} | ||
| 25 | + conditions[:type] = params[:content_type] if params[:content_type] #FIXME validate type | ||
| 26 | + conditions[:created_at] = begin_date...end_date | ||
| 27 | + present Article.find(:all, :conditions => conditions, :offset => (first_update.nil? ? 0 : 1), :limit => limit, :order => "created_at DESC"), :with => Entities::Article | ||
| 28 | + end | ||
| 29 | + | ||
| 30 | + #FIXME load article with environment context | ||
| 31 | + get ':id' do | ||
| 32 | + present Article.find(params[:id]), :with => Entities::Article | ||
| 17 | end | 33 | end |
| 18 | 34 | ||
| 19 | - limit = params[:limit].to_i | ||
| 20 | - limit = 20 if limit == 0 | ||
| 21 | - conditions = {} | ||
| 22 | - conditions[:type] = params[:content_type] if params[:content_type] #FIXME validate type | ||
| 23 | - conditions[:created_at] = begin_date...end_date | ||
| 24 | - Article.find(:all, :conditions => conditions, :offset => (first_update.nil? ? 0 : 1), :limit => limit, :order => "created_at DESC") | ||
| 25 | - end | ||
| 26 | - | ||
| 27 | - get ':id' do | ||
| 28 | - Article.find(params[:id]) | 35 | + #FIXME load article with environment context |
| 36 | + get ':id/children' do | ||
| 37 | + present Article.find(params[:id]).children, :with => Entities::Article | ||
| 38 | + end | ||
| 39 | + | ||
| 40 | + #FIXME load article with environment context | ||
| 41 | + get ':id/children/:child_id' do | ||
| 42 | + present Article.find(params[:id]).children.find(params[:child_id]), :with => Entities::Article | ||
| 43 | + end | ||
| 44 | + | ||
| 45 | + | ||
| 29 | end | 46 | end |
| 47 | + | ||
| 30 | end | 48 | end |
| 31 | - | ||
| 32 | - end | ||
| 33 | end | 49 | end |
| 34 | end | 50 | end |
lib/api/v1/comments.rb
| 1 | module API | 1 | module API |
| 2 | module V1 | 2 | module V1 |
| 3 | class Comments < Grape::API | 3 | class Comments < Grape::API |
| 4 | + | ||
| 5 | + before { authenticate! } | ||
| 4 | 6 | ||
| 5 | resource :articles do | 7 | resource :articles do |
| 6 | - | 8 | + #FIXME make the pagination |
| 9 | + #FIXME put it on environment context | ||
| 7 | get ":id/comments" do | 10 | get ":id/comments" do |
| 8 | - Article.find(params[:id]).comments | 11 | + present Article.find(params[:id]).comments, :with => Entities::Comment |
| 9 | end | 12 | end |
| 10 | 13 | ||
| 11 | get ":id/comments/:comment_id" do | 14 | get ":id/comments/:comment_id" do |
| 12 | - Article.find(params[:id]).comments.find(params[:comment_id]) | 15 | + present Article.find(params[:id]).comments.find(params[:comment_id]), :with => Entities::Comment |
| 13 | end | 16 | end |
| 14 | 17 | ||
| 15 | end | 18 | end |
| @@ -0,0 +1,43 @@ | @@ -0,0 +1,43 @@ | ||
| 1 | +module API | ||
| 2 | + module V1 | ||
| 3 | + class Users < Grape::API | ||
| 4 | + | ||
| 5 | + before { authenticate! } | ||
| 6 | + | ||
| 7 | + resource :users do | ||
| 8 | + | ||
| 9 | + #FIXME make the pagination | ||
| 10 | + #FIXME put it on environment context | ||
| 11 | +# get do | ||
| 12 | +# Users.all | ||
| 13 | +# end | ||
| 14 | + | ||
| 15 | + get ":id" do | ||
| 16 | + present Article.find(params[:id]).comments.find(params[:comment_id]), :with => Entities::User | ||
| 17 | + end | ||
| 18 | + | ||
| 19 | + # Create user. | ||
| 20 | + # | ||
| 21 | + # Parameters: | ||
| 22 | + # email (required) - Email | ||
| 23 | + # password (required) - Password | ||
| 24 | + # name - Name | ||
| 25 | + # Example Request: | ||
| 26 | + # POST /users | ||
| 27 | +# post do | ||
| 28 | + get do | ||
| 29 | +# authenticated_as_admin! | ||
| 30 | + required_attributes! [:email, :login, :password] | ||
| 31 | + attrs = attributes_for_keys [:email, :login, :password] | ||
| 32 | + user = User.new(attrs) | ||
| 33 | + if user.save | ||
| 34 | + present user, :with => Entities::User | ||
| 35 | + else | ||
| 36 | + not_found! | ||
| 37 | + end | ||
| 38 | + end | ||
| 39 | + end | ||
| 40 | + | ||
| 41 | + end | ||
| 42 | + end | ||
| 43 | +end |