apicasso 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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