apicasso 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,148 +1,148 @@
1
- # frozen_string_literal: true
2
-
3
- module Apicasso
4
- # Controller to consume read-only data to be used on client's frontend
5
- class CrudController < Apicasso::ApplicationController
6
- before_action :set_root_resource
7
- before_action :set_object, except: %i[index schema create]
8
- before_action :set_nested_resource, only: %i[nested_index]
9
- before_action :set_records, only: %i[index nested_index]
10
- before_action :set_schema, only: %i[schema]
11
-
12
- include Orderable
13
-
14
- # GET /:resource
15
- # Returns a paginated, ordered and filtered query based response.
16
- # Consider this
17
- # To get all `Channel` sorted by ascending `name` and descending
18
- # `updated_at`, filtered by the ones that have a `domain` that matches
19
- # exactly `"domain.com"`, paginating records 42 per page and retrieving
20
- # the page 42 of that collection. Usage:
21
- # GET /sites?sort=+name,-updated_at&q[domain_eq]=domain.com&page=42&per_page=42
22
- def index
23
- render json: response_json
24
- end
25
-
26
- # GET /:resource/1
27
- def show
28
- render json: @object.to_json(include: parsed_include)
29
- end
30
-
31
- # PATCH/PUT /:resource/1
32
- def update
33
- authorize_for(action: :update,
34
- resource: resource.name.underscore.to_sym,
35
- object: @object)
36
- if @object.update(object_params)
37
- render json: @object
38
- else
39
- render json: @object.errors, status: :unprocessable_entity
40
- end
41
- end
42
-
43
- # DELETE /:resource/1
44
- def destroy
45
- authorize_for(action: :destroy,
46
- resource: resource.name.underscore.to_sym,
47
- object: @object)
48
- if @object.destroy
49
- head :no_content
50
- else
51
- render json: @object.errors, status: :unprocessable_entity
52
- end
53
- end
54
-
55
- # GET /:resource/1/:nested_resource
56
- alias nested_index index
57
-
58
- # POST /:resource
59
- def create
60
- @object = resource.new(resource_params)
61
- authorize_for(action: :create,
62
- resource: resource.name.underscore.to_sym,
63
- object: @object)
64
- if @object.save
65
- render json: @object, status: :created, location: @object
66
- else
67
- render json: @object.errors, status: :unprocessable_entity
68
- end
69
- end
70
-
71
- # OPTIONS /:resource
72
- # OPTIONS /:resource/1/:nested_resource
73
- # Will return a JSON with the schema of the current resource, using
74
- # attribute names as keys and attirbute types as values.
75
- def schema
76
- render json: resource_schema.to_json
77
- end
78
-
79
- private
80
-
81
- # Common setup to stablish which model is the resource of this request
82
- def set_root_resource
83
- @root_resource = params[:resource].classify.constantize
84
- end
85
-
86
- # Common setup to stablish which object this request is querying
87
- def set_object
88
- id = params[:id]
89
- @object = resource.friendly.find(id)
90
- rescue NoMethodError
91
- @object = resource.find(id)
92
- ensure
93
- authorize! :read, @object
94
- end
95
-
96
- # Setup to stablish the nested model to be queried
97
- def set_nested_resource
98
- @nested_resource = @object.send(params[:nested].underscore.pluralize)
99
- end
100
-
101
- # Reutrns root_resource if nested_resource is not set scoped by permissions
102
- def resource
103
- (@nested_resource || @root_resource)
104
- end
105
-
106
- # Used to setup the resource's schema, mapping attributes and it's types
107
- def resource_schema
108
- schemated = {}
109
- resource.columns_hash.each { |key, value| schemated[key] = value.type }
110
- schemated
111
- end
112
-
113
- # Used to setup the records from the selected resource that are
114
- # going to be rendered, if authorized
115
- def set_records
116
- authorize! :read, resource.name.underscore.to_sym
117
- @records = resource.ransack(parsed_query).result
118
- reorder_records if params[:sort].present?
119
- end
120
-
121
- # Reordering of records which happens when receiving `params[:sort]`
122
- def reorder_records
123
- @records = @records.unscope(:order).order(ordering_params(params))
124
- end
125
-
126
- # Raw paginated records object
127
- def paginated_records
128
- @records.accessible_by(current_ability)
129
- .paginate(page: params[:page], per_page: params[:per_page])
130
- end
131
-
132
- # Parsing of `paginated_records` with pagination variables metadata
133
- def response_json
134
- { entries: entries_json }.merge(pagination_metadata_for(paginated_records))
135
- end
136
-
137
- # Parsed JSON to be used as response payload
138
- def entries_json
139
- JSON.parse(paginated_records.to_json(include: parsed_include))
140
- end
141
-
142
- # Only allow a trusted parameter "white list" through,
143
- # based on resource's schema.
144
- def object_params
145
- params.fetch(resource.name.underscore.to_sym, resource_schema.keys)
146
- end
147
- end
148
- end
1
+ # frozen_string_literal: true
2
+
3
+ module Apicasso
4
+ # Controller to consume read-only data to be used on client's frontend
5
+ class CrudController < Apicasso::ApplicationController
6
+ before_action :set_root_resource
7
+ before_action :set_object, except: %i[index schema create]
8
+ before_action :set_nested_resource, only: %i[nested_index]
9
+ before_action :set_records, only: %i[index nested_index]
10
+ before_action :set_schema, only: %i[schema]
11
+
12
+ include Orderable
13
+
14
+ # GET /:resource
15
+ # Returns a paginated, ordered and filtered query based response.
16
+ # Consider this
17
+ # To get all `Channel` sorted by ascending `name` and descending
18
+ # `updated_at`, filtered by the ones that have a `domain` that matches
19
+ # exactly `"domain.com"`, paginating records 42 per page and retrieving
20
+ # the page 42 of that collection. Usage:
21
+ # GET /sites?sort=+name,-updated_at&q[domain_eq]=domain.com&page=42&per_page=42
22
+ def index
23
+ render json: response_json
24
+ end
25
+
26
+ # GET /:resource/1
27
+ def show
28
+ render json: @object.to_json(include: parsed_include)
29
+ end
30
+
31
+ # PATCH/PUT /:resource/1
32
+ def update
33
+ authorize_for(action: :update,
34
+ resource: resource.name.underscore.to_sym,
35
+ object: @object)
36
+ if @object.update(object_params)
37
+ render json: @object
38
+ else
39
+ render json: @object.errors, status: :unprocessable_entity
40
+ end
41
+ end
42
+
43
+ # DELETE /:resource/1
44
+ def destroy
45
+ authorize_for(action: :destroy,
46
+ resource: resource.name.underscore.to_sym,
47
+ object: @object)
48
+ if @object.destroy
49
+ head :no_content
50
+ else
51
+ render json: @object.errors, status: :unprocessable_entity
52
+ end
53
+ end
54
+
55
+ # GET /:resource/1/:nested_resource
56
+ alias nested_index index
57
+
58
+ # POST /:resource
59
+ def create
60
+ @object = resource.new(resource_params)
61
+ authorize_for(action: :create,
62
+ resource: resource.name.underscore.to_sym,
63
+ object: @object)
64
+ if @object.save
65
+ render json: @object, status: :created, location: @object
66
+ else
67
+ render json: @object.errors, status: :unprocessable_entity
68
+ end
69
+ end
70
+
71
+ # OPTIONS /:resource
72
+ # OPTIONS /:resource/1/:nested_resource
73
+ # Will return a JSON with the schema of the current resource, using
74
+ # attribute names as keys and attirbute types as values.
75
+ def schema
76
+ render json: resource_schema.to_json
77
+ end
78
+
79
+ private
80
+
81
+ # Common setup to stablish which model is the resource of this request
82
+ def set_root_resource
83
+ @root_resource = params[:resource].classify.constantize
84
+ end
85
+
86
+ # Common setup to stablish which object this request is querying
87
+ def set_object
88
+ id = params[:id]
89
+ @object = resource.friendly.find(id)
90
+ rescue NoMethodError
91
+ @object = resource.find(id)
92
+ ensure
93
+ authorize! :read, @object
94
+ end
95
+
96
+ # Setup to stablish the nested model to be queried
97
+ def set_nested_resource
98
+ @nested_resource = @object.send(params[:nested].underscore.pluralize)
99
+ end
100
+
101
+ # Reutrns root_resource if nested_resource is not set scoped by permissions
102
+ def resource
103
+ (@nested_resource || @root_resource)
104
+ end
105
+
106
+ # Used to setup the resource's schema, mapping attributes and it's types
107
+ def resource_schema
108
+ schemated = {}
109
+ resource.columns_hash.each { |key, value| schemated[key] = value.type }
110
+ schemated
111
+ end
112
+
113
+ # Used to setup the records from the selected resource that are
114
+ # going to be rendered, if authorized
115
+ def set_records
116
+ authorize! :read, resource.name.underscore.to_sym
117
+ @records = resource.ransack(parsed_query).result
118
+ reorder_records if params[:sort].present?
119
+ end
120
+
121
+ # Reordering of records which happens when receiving `params[:sort]`
122
+ def reorder_records
123
+ @records = @records.unscope(:order).order(ordering_params(params))
124
+ end
125
+
126
+ # Raw paginated records object
127
+ def paginated_records
128
+ @records.accessible_by(current_ability)
129
+ .paginate(page: params[:page], per_page: params[:per_page])
130
+ end
131
+
132
+ # Parsing of `paginated_records` with pagination variables metadata
133
+ def response_json
134
+ { entries: entries_json }.merge(pagination_metadata_for(paginated_records))
135
+ end
136
+
137
+ # Parsed JSON to be used as response payload
138
+ def entries_json
139
+ JSON.parse(paginated_records.to_json(include: parsed_include))
140
+ end
141
+
142
+ # Only allow a trusted parameter "white list" through,
143
+ # based on resource's schema.
144
+ def object_params
145
+ params.fetch(resource.name.underscore.to_sym, resource_schema.keys)
146
+ end
147
+ end
148
+ end
@@ -1,44 +1,44 @@
1
- # frozen_string_literal: true
2
-
3
- # This concern is used to provide abstract ordering based on `params[:sort]`
4
- module Orderable
5
- extend ActiveSupport::Concern
6
- SORT_ORDER = { '+' => :asc, '-' => :desc }.freeze
7
-
8
- # A list of the param names that can be used for ordering the model list
9
- def ordering_params(params)
10
- # For example it retrieves a list of orders in descending order of total_value.
11
- # Within a specific total_value, older orders are ordered first
12
- #
13
- # GET /orders?sort=-total_value,created_at
14
- # ordering_params(params) # => { total_value: :desc, created_at: :asc }
15
- #
16
- # Usage:
17
- # Order.order(ordering_params(params))
18
- ordering = {}
19
- params[:sort].try(:split, ',').try(:each) do |attr|
20
- parsed_attr = parse_attr attr
21
- if model.attribute_names.include?(parsed_attr)
22
- ordering[parsed_attr] = SORT_ORDER[parse_sign parsed_attr]
23
- end
24
- end
25
- ordering
26
- end
27
-
28
- private
29
-
30
- # Parsing of attributes to avoid empty starts in case browser passes "+" as " "
31
- def parse_attr(attr)
32
- return attr.gsub(/^\ (.*)/, '\1') if attr.starts_with?(' ')
33
- attr[1..-1]
34
- end
35
-
36
- # Ordering sign parse, which separates
37
- def parse_sign(attr)
38
- attr =~ /\A[+-]/ ? attr.slice!(0) : '+'
39
- end
40
-
41
- def model
42
- (params[:resource] || params[:nested] || controller_name).classify.constantize
43
- end
44
- end
1
+ # frozen_string_literal: true
2
+
3
+ # This concern is used to provide abstract ordering based on `params[:sort]`
4
+ module Orderable
5
+ extend ActiveSupport::Concern
6
+ SORT_ORDER = { '+' => :asc, '-' => :desc }.freeze
7
+
8
+ # A list of the param names that can be used for ordering the model list
9
+ def ordering_params(params)
10
+ # For example it retrieves a list of orders in descending order of total_value.
11
+ # Within a specific total_value, older orders are ordered first
12
+ #
13
+ # GET /orders?sort=-total_value,created_at
14
+ # ordering_params(params) # => { total_value: :desc, created_at: :asc }
15
+ #
16
+ # Usage:
17
+ # Order.order(ordering_params(params))
18
+ ordering = {}
19
+ params[:sort].try(:split, ',').try(:each) do |attr|
20
+ parsed_attr = parse_attr attr
21
+ if model.attribute_names.include?(parsed_attr)
22
+ ordering[parsed_attr] = SORT_ORDER[parse_sign attr]
23
+ end
24
+ end
25
+ ordering
26
+ end
27
+
28
+ private
29
+
30
+ # Parsing of attributes to avoid empty starts in case browser passes "+" as " "
31
+ def parse_attr(attr)
32
+ return attr.gsub(/^\ (.*)/, '\1') if attr.starts_with?(' ')
33
+ attr[1..-1]
34
+ end
35
+
36
+ # Ordering sign parse, which separates
37
+ def parse_sign(attr)
38
+ attr =~ /\A[+-]/ ? attr.slice!(0) : '+'
39
+ end
40
+
41
+ def model
42
+ (params[:resource] || params[:nested] || controller_name).classify.constantize
43
+ end
44
+ end
@@ -1,4 +1,4 @@
1
- module Apicasso
2
- module ApplicationHelper
3
- end
4
- end
1
+ module Apicasso
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -1,4 +1,4 @@
1
- module Apicasso
2
- class ApplicationJob < ActiveJob::Base
3
- end
4
- end
1
+ module Apicasso
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -1,6 +1,6 @@
1
- module Apicasso
2
- class ApplicationMailer < ActionMailer::Base
3
- default from: 'from@example.com'
4
- layout 'mailer'
5
- end
6
- end
1
+ module Apicasso
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: 'from@example.com'
4
+ layout 'mailer'
5
+ end
6
+ end
@@ -1,40 +1,40 @@
1
- # frozen_string_literal: true
2
-
3
- module Apicasso
4
- # Ability to parse a scope object from Apicasso::Key
5
- class Ability
6
- include CanCan::Ability
7
-
8
- def initialize(key)
9
- key ||= Apicasso::Key.new
10
- cannot :manage, :all
11
- cannot :read, :all
12
- key.scope.each do |permission, klasses_clearances|
13
- klasses_clearances.each do |klasses|
14
- klasses.each do |klass, clearance|
15
- if clearance == true
16
- # Usage:
17
- # To have a key reading all channels and all accouts
18
- # you would have a scope:
19
- # => `{read: [{channel: true}, {accout: true}]}`
20
- can permission.to_sym, klass.underscore.to_sym
21
- can permission.to_sym, klass.classify.constantize
22
- elsif clearance.class == Hash
23
- # Usage:
24
- # To have a key reading all banners from a channel with id 999
25
- # you would have a scope:
26
- # => `{read: [{banner: {owner_id: [999]}}]}`
27
- can permission.to_sym,
28
- klass.underscore.to_sym
29
- clearance.each do |by_field, values|
30
- can permission.to_sym,
31
- klass.classify.constantize,
32
- by_field => values
33
- end
34
- end
35
- end
36
- end
37
- end
38
- end
39
- end
40
- end
1
+ # frozen_string_literal: true
2
+
3
+ module Apicasso
4
+ # Ability to parse a scope object from Apicasso::Key
5
+ class Ability
6
+ include CanCan::Ability
7
+
8
+ def initialize(key)
9
+ key ||= Apicasso::Key.new
10
+ cannot :manage, :all
11
+ cannot :read, :all
12
+ key.scope.each do |permission, klasses_clearances|
13
+ klasses_clearances.each do |klasses|
14
+ klasses.each do |klass, clearance|
15
+ if clearance == true
16
+ # Usage:
17
+ # To have a key reading all channels and all accouts
18
+ # you would have a scope:
19
+ # => `{read: [{channel: true}, {accout: true}]}`
20
+ can permission.to_sym, klass.underscore.to_sym
21
+ can permission.to_sym, klass.classify.constantize
22
+ elsif clearance.class == Hash
23
+ # Usage:
24
+ # To have a key reading all banners from a channel with id 999
25
+ # you would have a scope:
26
+ # => `{read: [{banner: {owner_id: [999]}}]}`
27
+ can permission.to_sym,
28
+ klass.underscore.to_sym
29
+ clearance.each do |by_field, values|
30
+ can permission.to_sym,
31
+ klass.classify.constantize,
32
+ by_field => values
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -1,6 +1,6 @@
1
- module Apicasso
2
- class ApplicationRecord < ActiveRecord::Base
3
- self.abstract_class = true
4
- self.table_name_prefix = 'apicasso_'
5
- end
6
- end
1
+ module Apicasso
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ self.table_name_prefix = 'apicasso_'
5
+ end
6
+ end
@@ -1,25 +1,25 @@
1
- # frozen_string_literal: true
2
-
3
- require 'securerandom'
4
- module Apicasso
5
- # A model to abstract API access, with scope options, token generation, request limiting
6
- class Key < ApplicationRecord
7
- before_create :set_auth_token
8
-
9
- private
10
-
11
- # Method that generates `SecureRandom.uuid` as token until
12
- # an unique one has been acquired
13
- def set_auth_token
14
- loop do
15
- self.token = generate_auth_token
16
- break unless self.class.exists?(token: token)
17
- end
18
- end
19
-
20
- # RFC4122 style token
21
- def generate_auth_token
22
- SecureRandom.uuid.delete('-')
23
- end
24
- end
25
- end
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+ module Apicasso
5
+ # A model to abstract API access, with scope options, token generation, request limiting
6
+ class Key < ApplicationRecord
7
+ before_create :set_auth_token
8
+
9
+ private
10
+
11
+ # Method that generates `SecureRandom.uuid` as token until
12
+ # an unique one has been acquired
13
+ def set_auth_token
14
+ loop do
15
+ self.token = generate_auth_token
16
+ break unless self.class.exists?(token: token)
17
+ end
18
+ end
19
+
20
+ # RFC4122 style token
21
+ def generate_auth_token
22
+ SecureRandom.uuid.delete('-')
23
+ end
24
+ end
25
+ end
@@ -1,8 +1,8 @@
1
- # frozen_string_literal: true
2
-
3
- module Apicasso
4
- # A model to abstract API access, with scope options, token generation, request limiting
5
- class Request < ApplicationRecord
6
- belongs_to :api_key, class_name: 'Apicasso::Key'
7
- end
8
- end
1
+ # frozen_string_literal: true
2
+
3
+ module Apicasso
4
+ # A model to abstract API access, with scope options, token generation, request limiting
5
+ class Request < ApplicationRecord
6
+ belongs_to :api_key, class_name: 'Apicasso::Key'
7
+ end
8
+ end
@@ -1,16 +1,16 @@
1
- <!DOCTYPE html>
2
- <html>
3
- <head>
4
- <title>Apicasso</title>
5
- <%= csrf_meta_tags %>
6
- <%= csp_meta_tag %>
7
-
8
- <%= stylesheet_link_tag "apicasso/application", media: "all" %>
9
- <%= javascript_include_tag "apicasso/application" %>
10
- </head>
11
- <body>
12
-
13
- <%= yield %>
14
-
15
- </body>
16
- </html>
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Apicasso</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+
8
+ <%= stylesheet_link_tag "apicasso/application", media: "all" %>
9
+ <%= javascript_include_tag "apicasso/application" %>
10
+ </head>
11
+ <body>
12
+
13
+ <%= yield %>
14
+
15
+ </body>
16
+ </html>
data/config/routes.rb CHANGED
@@ -1,13 +1,13 @@
1
- Apicasso::Engine.routes.draw do
2
- scope module: :apicasso do
3
- get '/:resource/', to: 'crud#index', via: :get
4
- match '/:resource/', to: 'crud#create', via: :post
5
- get '/:resource/:id', to: 'crud#show', via: :get
6
- match '/:resource/:id', to: 'crud#update', via: :patch
7
- match '/:resource/:id', to: 'crud#destroy', via: :delete
8
- match '/:resource/:id/:nested/', to: 'crud#nested_index', via: :get
9
- match '/:resource/', to: 'crud#schema', via: :options
10
- match '/:resource/:id/:nested/', to: 'crud#schema', via: :options
11
- resources :apidocs, only: [:index]
12
- end
13
- end
1
+ Apicasso::Engine.routes.draw do
2
+ scope module: :apicasso do
3
+ get '/:resource/', to: 'crud#index', via: :get
4
+ match '/:resource/', to: 'crud#create', via: :post
5
+ get '/:resource/:id', to: 'crud#show', via: :get
6
+ match '/:resource/:id', to: 'crud#update', via: :patch
7
+ match '/:resource/:id', to: 'crud#destroy', via: :delete
8
+ match '/:resource/:id/:nested/', to: 'crud#nested_index', via: :get
9
+ match '/:resource/', to: 'crud#schema', via: :options
10
+ match '/:resource/:id/:nested/', to: 'crud#schema', via: :options
11
+ resources :apidocs, only: [:index]
12
+ end
13
+ end
File without changes
@@ -1,6 +1,6 @@
1
- # frozen_string_literal: true
2
-
3
- module Apicasso
4
- class Engine < ::Rails::Engine
5
- end
6
- end
1
+ # frozen_string_literal: true
2
+
3
+ module Apicasso
4
+ class Engine < ::Rails::Engine
5
+ end
6
+ end
@@ -1,3 +1,3 @@
1
- module Apicasso
2
- VERSION = '0.1.2'
3
- end
1
+ module Apicasso
2
+ VERSION = '0.1.3'
3
+ end