thecore_api 1.1.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/Rakefile +37 -0
- data/app/controllers/api/v1/base_controller.rb +192 -0
- data/app/controllers/api/v1/info_controller.rb +40 -0
- data/app/controllers/api/v1/sessions_controller.rb +21 -0
- data/app/controllers/api/v1/users_controller.rb +15 -0
- data/config/initializers/wrap_parameters.rb +14 -0
- data/config/routes.rb +21 -0
- data/lib/tasks/thecore_api_tasks.rake +4 -0
- data/lib/thecore_api.rb +11 -0
- data/lib/thecore_api/engine.rb +18 -0
- data/lib/thecore_api/version.rb +3 -0
- data/test/dummy/README.rdoc +28 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/javascripts/application.js +13 -0
- data/test/dummy/app/assets/stylesheets/application.css +15 -0
- data/test/dummy/app/controllers/application_controller.rb +5 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +4 -0
- data/test/dummy/bin/rake +4 -0
- data/test/dummy/bin/setup +29 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +26 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +41 -0
- data/test/dummy/config/environments/production.rb +79 -0
- data/test/dummy/config/environments/test.rb +42 -0
- data/test/dummy/config/initializers/assets.rb +11 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +4 -0
- data/test/dummy/config/initializers/session_store.rb +3 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +23 -0
- data/test/dummy/config/routes.rb +56 -0
- data/test/dummy/config/secrets.yml +22 -0
- data/test/dummy/public/404.html +67 -0
- data/test/dummy/public/422.html +67 -0
- data/test/dummy/public/500.html +66 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/integration/navigation_test.rb +8 -0
- data/test/test_helper.rb +20 -0
- data/test/thecore_api_test.rb +7 -0
- metadata +200 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c3eeb066a19ef99dc62d57f971cd85863285dc5e
|
4
|
+
data.tar.gz: 65a10141e57a771479610214ae7f90f7084ca70e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 66da97430ea31f832e402d9f9ec1eb610314660c380d0075a7576909d8e01e477e28f89b4f02cba058dae30404a54eed1a34fa501b630f484f949a18072d0434
|
7
|
+
data.tar.gz: 9c7619c6fbd0bf84c151b99915669c42341726935971474a2c9b13da15ee6a091c321eff2a95052f44cae65bc45334526341bfbd3baa9dcc632bd9f228f59f79
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2016 Gabriele Tassoni
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'ThecoreApi'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.rdoc')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
|
18
|
+
load 'rails/tasks/engine.rake'
|
19
|
+
|
20
|
+
|
21
|
+
load 'rails/tasks/statistics.rake'
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
Bundler::GemHelper.install_tasks
|
26
|
+
|
27
|
+
require 'rake/testtask'
|
28
|
+
|
29
|
+
Rake::TestTask.new(:test) do |t|
|
30
|
+
t.libs << 'lib'
|
31
|
+
t.libs << 'test'
|
32
|
+
t.pattern = 'test/**/*_test.rb'
|
33
|
+
t.verbose = false
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
task default: :test
|
@@ -0,0 +1,192 @@
|
|
1
|
+
# https://labs.kollegorna.se/blog/2015/04/build-an-api-now/
|
2
|
+
class Api::V1::BaseController < ActionController::API
|
3
|
+
include CanCan::ControllerAdditions
|
4
|
+
#include Pundit
|
5
|
+
#check_authorization
|
6
|
+
#load_and_authorize_resource
|
7
|
+
# https://github.com/kollegorna/active_hash_relation
|
8
|
+
include ActiveHashRelation
|
9
|
+
|
10
|
+
# Prevent CSRF attacks by raising an exception.
|
11
|
+
# For APIs, you may want to use :null_session instead.
|
12
|
+
#protect_from_forgery with: :null_session
|
13
|
+
|
14
|
+
before_action :destroy_session
|
15
|
+
|
16
|
+
before_action :authenticate_user!
|
17
|
+
before_action :find_model, except: [ :version, :token ]
|
18
|
+
before_action :find_record, only: [ :show, :update, :destroy ]
|
19
|
+
|
20
|
+
rescue_from ActiveRecord::RecordNotFound, with: :not_found!
|
21
|
+
rescue_from ActiveRecord::StatementInvalid, with: :unauthenticated!
|
22
|
+
rescue_from ActiveRecord::RecordInvalid, with: :invalid!
|
23
|
+
#rescue_from CanCan::AuthorizationNotPerformed, with: :unauthorized!
|
24
|
+
rescue_from CanCan::AccessDenied, with: :unauthorized!
|
25
|
+
#rescue_from Pundit::NotAuthorizedError, with: :unauthorized!
|
26
|
+
|
27
|
+
attr_accessor :current_user
|
28
|
+
|
29
|
+
def index
|
30
|
+
# find the records
|
31
|
+
@q = (@model.column_names.include?("user_id") ? @model.where(user_id: current_user.id) : @model).ransack(params[:q])
|
32
|
+
@records_all = @q.result(distinct: true)
|
33
|
+
@records = @records_all.page(params[:page]).per(params[:per])
|
34
|
+
|
35
|
+
# If there's the keyword pagination_info, then return a pagination info object
|
36
|
+
return render json: MultiJson.dump({
|
37
|
+
count: @records_all.count,
|
38
|
+
current_page_count: @records.count,
|
39
|
+
next_page: @records.next_page,
|
40
|
+
prev_page: @records.prev_page,
|
41
|
+
is_first_page: @records.first_page?,
|
42
|
+
is_last_page: @records.last_page?,
|
43
|
+
is_out_of_range: @records.out_of_range?,
|
44
|
+
pages_count: @records.total_pages,
|
45
|
+
current_page_number: @records.current_page
|
46
|
+
}) if !params[:pages_info].nil?
|
47
|
+
# If it's asked for page number, the paginate
|
48
|
+
return render json: MultiJson.dump(@records, @json_attrs || {}) if !params[:page].nil? # (@json_attrs || {})
|
49
|
+
# if you ask for count, then return a json object with just the number of objects
|
50
|
+
return render json: MultiJson.dump({count: @records_all.count}) if !params[:count].nil?
|
51
|
+
# Default
|
52
|
+
render json: MultiJson.dump(@records_all, @json_attrs || {}) #(@json_attrs || {})
|
53
|
+
end
|
54
|
+
|
55
|
+
# def count
|
56
|
+
# # find the records
|
57
|
+
# @q = (@model.column_names.include?("user_id") ? @model.where(user_id: current_user.id) : @model).ransack(params[:q])
|
58
|
+
# @records_all = @q.result(distinct: true)
|
59
|
+
# # if you ask for count, then return a json object with just the number of objects
|
60
|
+
# return render json: {count: @records_all.count}.to_json
|
61
|
+
# end
|
62
|
+
|
63
|
+
def search
|
64
|
+
index
|
65
|
+
render :index
|
66
|
+
end
|
67
|
+
|
68
|
+
def show
|
69
|
+
render json: @record.to_json(@json_attrs || {}), status: 200
|
70
|
+
end
|
71
|
+
|
72
|
+
def create
|
73
|
+
@record = @model.new(request_params)
|
74
|
+
@record.user_id = current_user.id if @model.column_names.include? "user_id"
|
75
|
+
|
76
|
+
@record.save!
|
77
|
+
|
78
|
+
render json: @record.to_json(@json_attrs || {}), status: 201
|
79
|
+
end
|
80
|
+
|
81
|
+
def update
|
82
|
+
@record.update_attributes(request_params)
|
83
|
+
|
84
|
+
render json: @record.to_json(@json_attrs || {}), status: 200
|
85
|
+
end
|
86
|
+
|
87
|
+
def destroy
|
88
|
+
unless @record.destroy
|
89
|
+
return api_error(status: 500)
|
90
|
+
end
|
91
|
+
|
92
|
+
head status: 204
|
93
|
+
end
|
94
|
+
|
95
|
+
protected
|
96
|
+
|
97
|
+
def destroy_session
|
98
|
+
request.session_options[:skip] = true
|
99
|
+
end
|
100
|
+
|
101
|
+
def unauthenticated!
|
102
|
+
response.headers['WWW-Authenticate'] = "Token realm=Application"
|
103
|
+
render json: { error: 'bad credentials' }, status: 401
|
104
|
+
end
|
105
|
+
|
106
|
+
def unauthorized!
|
107
|
+
render nothing: true, status: :forbidden
|
108
|
+
return
|
109
|
+
end
|
110
|
+
|
111
|
+
def invalid_credentials!
|
112
|
+
render json: { error: 'invalid credentials' }, status: 403
|
113
|
+
return
|
114
|
+
end
|
115
|
+
|
116
|
+
def unable!
|
117
|
+
render json: { error: 'you are not enabled to do so' }, status: 403
|
118
|
+
end
|
119
|
+
|
120
|
+
def invalid!
|
121
|
+
render json: { error: 'Some validation has failed' }, status: 422
|
122
|
+
end
|
123
|
+
|
124
|
+
def invalid_resource!(errors = [])
|
125
|
+
api_error(status: 422, errors: errors)
|
126
|
+
end
|
127
|
+
|
128
|
+
def not_found!
|
129
|
+
return api_error(status: 404, errors: 'Not found')
|
130
|
+
end
|
131
|
+
|
132
|
+
def api_error(status: 500, errors: [])
|
133
|
+
unless Rails.env.production?
|
134
|
+
puts errors.full_messages if errors.respond_to? :full_messages
|
135
|
+
end
|
136
|
+
head status: status and return if errors.empty?
|
137
|
+
|
138
|
+
# render json: jsonapi_format(errors).to_json, status: status
|
139
|
+
render json: errors.to_json, status: status
|
140
|
+
end
|
141
|
+
|
142
|
+
def paginate(resource)
|
143
|
+
resource = resource.page(params[:page] || 1)
|
144
|
+
if params[:per_page]
|
145
|
+
resource = resource.per_page(params[:per_page])
|
146
|
+
end
|
147
|
+
|
148
|
+
return resource
|
149
|
+
end
|
150
|
+
|
151
|
+
# expects pagination!
|
152
|
+
def meta_attributes(object)
|
153
|
+
{
|
154
|
+
current_page: object.current_page,
|
155
|
+
next_page: object.next_page,
|
156
|
+
prev_page: (object.previous_page rescue nil),
|
157
|
+
total_pages: object.total_pages,
|
158
|
+
total_count: (object.total_entries rescue nil)
|
159
|
+
}
|
160
|
+
end
|
161
|
+
|
162
|
+
def authenticate_user!
|
163
|
+
token, options = ActionController::HttpAuthentication::Token.token_and_options(request)
|
164
|
+
|
165
|
+
# puts "controller: #{controller_name}\naction: #{action_name}\ntoken: #{token}\noptions: #{options.inspect}\n\n"
|
166
|
+
|
167
|
+
user_email = options.blank?? nil : options[:email]
|
168
|
+
user = user_email && User.find_by(email: user_email)
|
169
|
+
|
170
|
+
if user && ActiveSupport::SecurityUtils.secure_compare(user.authentication_token, token)
|
171
|
+
@current_user = user
|
172
|
+
else
|
173
|
+
return unauthenticated!
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
private
|
178
|
+
|
179
|
+
def find_record
|
180
|
+
# find the records
|
181
|
+
@record = @model.column_names.include?("user_id") ? @model.where(id: params[:id], user_id: current_user.id).first : @model.find(params[:id])
|
182
|
+
end
|
183
|
+
|
184
|
+
def find_model
|
185
|
+
# Find the name of the model from controller
|
186
|
+
@model = controller_path.classify.constantize rescue controller_name.classify.constantize
|
187
|
+
end
|
188
|
+
|
189
|
+
def request_params
|
190
|
+
params.require(controller_name.to_sym).permit!().delete_if{ |k,v| v.nil? }
|
191
|
+
end
|
192
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
class Api::V1::InfoController < Api::V1::BaseController
|
2
|
+
skip_before_action :authenticate_user!, only: [:version]
|
3
|
+
|
4
|
+
# api :GET, '/api/v1/info/version', "Just prints the APPVERSION."
|
5
|
+
# api!
|
6
|
+
def version
|
7
|
+
render json: {
|
8
|
+
version: APPVERSION
|
9
|
+
}.to_json, status: 200
|
10
|
+
end
|
11
|
+
|
12
|
+
# api :GET, '/api/v1/info/token', "Given auth credentials, in HTTP_BASIC form,
|
13
|
+
# it returns the AUTH_TOKEN, email and id of the user which performed the authentication."
|
14
|
+
# api!
|
15
|
+
def token
|
16
|
+
render json: {
|
17
|
+
token: @current_user.authentication_token,
|
18
|
+
email: @current_user.email,
|
19
|
+
roles: @current_user.roles,
|
20
|
+
admin: @current_user.admin,
|
21
|
+
third_party: @current_user.third_party,
|
22
|
+
id: @current_user.id
|
23
|
+
}.to_json, status: 200
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
# Method overridden because the first time I have to ask for the token
|
29
|
+
def authenticate_user!
|
30
|
+
username, password = ActionController::HttpAuthentication::Basic.user_name_and_password(request)
|
31
|
+
if username
|
32
|
+
user = User.find_by(username: username)
|
33
|
+
end
|
34
|
+
if user && user.valid_password?(password)
|
35
|
+
@current_user = user
|
36
|
+
else
|
37
|
+
return unauthenticated!
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class Api::V1::SessionsController < Api::V1::BaseController
|
2
|
+
skip_before_action :authenticate_user!
|
3
|
+
def create
|
4
|
+
user = User.find_by(email: request_params[:email])
|
5
|
+
if user && user.authenticate(request_params[:password])
|
6
|
+
self.current_user = user
|
7
|
+
render(
|
8
|
+
json: Api::V1::SessionSerializer.new(user, root: false).to_json,
|
9
|
+
status: 201
|
10
|
+
)
|
11
|
+
else
|
12
|
+
return api_error(status: 401)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def request_params
|
19
|
+
params.require(:user).permit(:email, :password)
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class Api::V1::UsersController < Api::V1::BaseController
|
2
|
+
load_and_authorize_resource
|
3
|
+
|
4
|
+
before_action :check_demoting
|
5
|
+
|
6
|
+
private
|
7
|
+
|
8
|
+
def check_demoting
|
9
|
+
render json: "You cannot demote yourself", status: 403 if (params[:id].to_i == current_user.id && (params[:users].keys.include?("admin") || params[:users].keys.include?("locked")))
|
10
|
+
end
|
11
|
+
|
12
|
+
def request_params
|
13
|
+
params.require(:users).permit(:email, :roles, :password, :password_confirmation, :username, :number_of_instances_purchased, :admin, :locked).delete_if{ |k,v| v.nil? }
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# Be sure to restart your server when you modify this file.
|
2
|
+
|
3
|
+
# This file contains settings for ActionController::ParamsWrapper which
|
4
|
+
# is enabled by default.
|
5
|
+
|
6
|
+
# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
|
7
|
+
ActiveSupport.on_load(:action_controller) do
|
8
|
+
wrap_parameters format: [:json] if respond_to?(:wrap_parameters)
|
9
|
+
end
|
10
|
+
|
11
|
+
# To enable root element in JSON for ActiveRecord objects.
|
12
|
+
# ActiveSupport.on_load(:active_record) do
|
13
|
+
# self.include_root_in_json = true
|
14
|
+
# end
|
data/config/routes.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'thecore'
|
2
|
+
require 'ransack'
|
3
|
+
|
4
|
+
Rails.application.routes.draw do
|
5
|
+
# REST API (Stateless)
|
6
|
+
namespace :api do
|
7
|
+
namespace :v1, defaults: { format: :json } do
|
8
|
+
|
9
|
+
resources :sessions, only: [:create]
|
10
|
+
|
11
|
+
namespace :info do
|
12
|
+
get :version
|
13
|
+
get :token
|
14
|
+
end
|
15
|
+
|
16
|
+
resources :users, only: [:index, :create, :show, :update, :destroy] do
|
17
|
+
match 'search' => 'users#search', via: [:get, :post], as: :search, on: :collection
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/thecore_api.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'thecore'
|
2
|
+
# require 'rails-api' integrated into rails 5.0
|
3
|
+
# require 'active_model_serializers'
|
4
|
+
require 'active_hash_relation'
|
5
|
+
require 'rack/cors'
|
6
|
+
require 'therubyracer'
|
7
|
+
require "thecore_api/engine"
|
8
|
+
require 'ransack'
|
9
|
+
|
10
|
+
module ThecoreApi
|
11
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module ThecoreApi
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
# appending migrations to the main app's ones
|
4
|
+
initializer :append_migrations do |app|
|
5
|
+
config.middleware.insert_before 0, "Rack::Cors" do
|
6
|
+
allow do
|
7
|
+
origins '*'
|
8
|
+
resource '*', :headers => :any, :methods => [:get, :post, :put, :patch, :delete, :options, :head]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
unless app.root.to_s == root.to_s
|
12
|
+
config.paths["db/migrate"].expanded.each do |expanded_path|
|
13
|
+
app.config.paths["db/migrate"] << expanded_path
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
== README
|
2
|
+
|
3
|
+
This README would normally document whatever steps are necessary to get the
|
4
|
+
application up and running.
|
5
|
+
|
6
|
+
Things you may want to cover:
|
7
|
+
|
8
|
+
* Ruby version
|
9
|
+
|
10
|
+
* System dependencies
|
11
|
+
|
12
|
+
* Configuration
|
13
|
+
|
14
|
+
* Database creation
|
15
|
+
|
16
|
+
* Database initialization
|
17
|
+
|
18
|
+
* How to run the test suite
|
19
|
+
|
20
|
+
* Services (job queues, cache servers, search engines, etc.)
|
21
|
+
|
22
|
+
* Deployment instructions
|
23
|
+
|
24
|
+
* ...
|
25
|
+
|
26
|
+
|
27
|
+
Please feel free to use a different markup language if you do not plan to run
|
28
|
+
<tt>rake doc:app</tt>.
|