croods 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +28 -0
  4. data/Rakefile +19 -0
  5. data/lib/croods.rb +58 -0
  6. data/lib/croods/action.rb +17 -0
  7. data/lib/croods/api.rb +26 -0
  8. data/lib/croods/api/initial_schema.json +52 -0
  9. data/lib/croods/attribute.rb +15 -0
  10. data/lib/croods/controller.rb +28 -0
  11. data/lib/croods/controller/actions.rb +71 -0
  12. data/lib/croods/controller/already_taken.rb +27 -0
  13. data/lib/croods/controller/authentication.rb +22 -0
  14. data/lib/croods/controller/authorization.rb +31 -0
  15. data/lib/croods/controller/collection.rb +15 -0
  16. data/lib/croods/controller/member.rb +45 -0
  17. data/lib/croods/controller/model.rb +17 -0
  18. data/lib/croods/controller/multi_tenancy.rb +54 -0
  19. data/lib/croods/controller/not_found.rb +22 -0
  20. data/lib/croods/controller/record_invalid.rb +22 -0
  21. data/lib/croods/controller/resource.rb +30 -0
  22. data/lib/croods/middleware.rb +44 -0
  23. data/lib/croods/middleware/base.rb +20 -0
  24. data/lib/croods/middleware/request_validation.rb +10 -0
  25. data/lib/croods/middleware/response_validation.rb +10 -0
  26. data/lib/croods/model.rb +41 -0
  27. data/lib/croods/policy.rb +118 -0
  28. data/lib/croods/policy/scope.rb +131 -0
  29. data/lib/croods/railtie.rb +6 -0
  30. data/lib/croods/resource.rb +39 -0
  31. data/lib/croods/resource/actions.rb +51 -0
  32. data/lib/croods/resource/attributes.rb +53 -0
  33. data/lib/croods/resource/attributes/base.rb +30 -0
  34. data/lib/croods/resource/attributes/request.rb +29 -0
  35. data/lib/croods/resource/attributes/response.rb +13 -0
  36. data/lib/croods/resource/authentication.rb +46 -0
  37. data/lib/croods/resource/authorization.rb +47 -0
  38. data/lib/croods/resource/controller.rb +50 -0
  39. data/lib/croods/resource/filters.rb +32 -0
  40. data/lib/croods/resource/identifier.rb +13 -0
  41. data/lib/croods/resource/json_schema.rb +32 -0
  42. data/lib/croods/resource/json_schema/definition.rb +34 -0
  43. data/lib/croods/resource/json_schema/definitions.rb +31 -0
  44. data/lib/croods/resource/json_schema/initial_schema.json +8 -0
  45. data/lib/croods/resource/json_schema/links.rb +40 -0
  46. data/lib/croods/resource/json_schema/links/collection.rb +70 -0
  47. data/lib/croods/resource/json_schema/links/create.rb +41 -0
  48. data/lib/croods/resource/json_schema/links/destroy.rb +40 -0
  49. data/lib/croods/resource/json_schema/links/index.rb +17 -0
  50. data/lib/croods/resource/json_schema/links/member.rb +40 -0
  51. data/lib/croods/resource/json_schema/links/show.rb +40 -0
  52. data/lib/croods/resource/json_schema/links/update.rb +40 -0
  53. data/lib/croods/resource/json_schema/properties.rb +32 -0
  54. data/lib/croods/resource/json_schema/required.rb +34 -0
  55. data/lib/croods/resource/model.rb +35 -0
  56. data/lib/croods/resource/names.rb +23 -0
  57. data/lib/croods/resource/paths.rb +16 -0
  58. data/lib/croods/resource/policy.rb +51 -0
  59. data/lib/croods/resource/services.rb +18 -0
  60. data/lib/croods/resource/sorting.rb +13 -0
  61. data/lib/croods/routes.rb +46 -0
  62. data/lib/croods/service.rb +28 -0
  63. data/lib/croods/version.rb +5 -0
  64. data/lib/tasks/croods_tasks.rake +4 -0
  65. metadata +316 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: aa04a8d021dcb13f1a16fb7e8cbff426ff88212cc2961f7b6ab37c11dec6205a
4
+ data.tar.gz: 6eaf5e2422fc7673d79af1f371bfc3d84f667beabf7725be8bfb979f56f211b5
5
+ SHA512:
6
+ metadata.gz: e3c2a7f41a1684b68d97fd5b5157a0827cc4921c187de659c328762220ac2f1fe47cbf450a9240e665b7ae908ea4814f7e625d563640d305e843369aa8e8f29e
7
+ data.tar.gz: 583726e9874be44c8817d082ec6286cadec710be19eac125ee3251f1aeaacc11e2268311fd36eeb936faad066273861984602bb7205ad6a5ed323af94127d3ab
@@ -0,0 +1,20 @@
1
+ Copyright 2020 Daniel Weinmann
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.
@@ -0,0 +1,28 @@
1
+ # Croods
2
+ Short description and motivation.
3
+
4
+ ## Usage
5
+ How to use my plugin.
6
+
7
+ ## Installation
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'croods'
12
+ ```
13
+
14
+ And then execute:
15
+ ```bash
16
+ $ bundle
17
+ ```
18
+
19
+ Or install it yourself as:
20
+ ```bash
21
+ $ gem install croods
22
+ ```
23
+
24
+ ## Contributing
25
+ Contribution directions go here.
26
+
27
+ ## License
28
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require 'bundler/setup'
5
+ rescue LoadError
6
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
7
+ end
8
+
9
+ require 'rdoc/task'
10
+
11
+ RDoc::Task.new(:rdoc) do |rdoc|
12
+ rdoc.rdoc_dir = 'rdoc'
13
+ rdoc.title = 'Croods'
14
+ rdoc.options << '--line-numbers'
15
+ rdoc.rdoc_files.include('README.md')
16
+ rdoc.rdoc_files.include('lib/**/*.rb')
17
+ end
18
+
19
+ require 'bundler/gem_tasks'
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rack/cors'
4
+ require 'committee'
5
+ require 'devise'
6
+ require 'devise_token_auth'
7
+ require 'pundit'
8
+ require 'schema_associations'
9
+ require 'schema_auto_foreign_keys'
10
+ require 'schema_validations'
11
+
12
+ module Croods
13
+ cattr_accessor :namespaces, :json_schema, :multi_tenancy_by
14
+
15
+ class << self
16
+ def initialize_for(*namespaces, multi_tenancy_by: nil)
17
+ self.multi_tenancy_by = multi_tenancy_by
18
+ self.namespaces = namespaces.map(&:to_s).freeze
19
+ Middleware.insert!
20
+ end
21
+
22
+ def resources
23
+ namespaces.map do |namespace|
24
+ "#{namespace.camelcase(:upper)}::Resource".constantize
25
+ end
26
+ end
27
+
28
+ def multi_tenancy?
29
+ !multi_tenancy_by.nil?
30
+ end
31
+
32
+ def tenant_attribute
33
+ "#{Croods.multi_tenancy_by}_id".to_sym
34
+ end
35
+
36
+ def application_controller(&block)
37
+ return unless block
38
+
39
+ application_controller_blocks << block
40
+ end
41
+
42
+ def application_controller_blocks
43
+ @application_controller_blocks ||= []
44
+ end
45
+ end
46
+ end
47
+
48
+ require 'croods/railtie'
49
+ require 'croods/model'
50
+ require 'croods/attribute'
51
+ require 'croods/controller'
52
+ require 'croods/service'
53
+ require 'croods/action'
54
+ require 'croods/policy'
55
+ require 'croods/resource'
56
+ require 'croods/routes'
57
+ require 'croods/middleware'
58
+ require 'croods/api'
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Croods
4
+ class Action
5
+ attr_accessor :name, :public, :roles, :method, :on, :block, :service
6
+
7
+ def initialize(name, **options)
8
+ self.name = name.to_sym
9
+ self.public = options[:public]
10
+ self.roles = options[:roles]
11
+ self.method = options[:method]
12
+ self.on = options[:on]
13
+ self.block = options[:block]
14
+ self.service = options[:service]
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Croods
4
+ module Api
5
+ class << self
6
+ def initial_schema
7
+ File.read(File.expand_path('api/initial_schema.json', __dir__))
8
+ end
9
+
10
+ def json_schema
11
+ schema = JSON.parse(initial_schema)
12
+
13
+ Croods.resources.each do |resource|
14
+ next unless resource.table_exists?
15
+
16
+ name = resource.resource_name
17
+ schema['definitions'][name] = resource.json_schema
18
+ schema['properties'][name] = resource.ref
19
+ end
20
+
21
+ schema.deep_stringify_keys!
22
+ Committee::Drivers::HyperSchema::Driver.new.parse(schema)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,52 @@
1
+ {
2
+ "$schema": "http://interagent.github.io/interagent-hyper-schema",
3
+ "type": [
4
+ "object"
5
+ ],
6
+ "definitions": {
7
+ "error": {
8
+ "$schema": "http://json-schema.org/draft-04/hyper-schema",
9
+ "stability": "prototype",
10
+ "strictProperties": true,
11
+ "type": [
12
+ "object"
13
+ ],
14
+ "definitions": {
15
+ "id": {
16
+ "readOnly": true,
17
+ "pattern": "^\\w+$",
18
+ "type": [
19
+ "string"
20
+ ]
21
+ },
22
+ "message": {
23
+ "readOnly": true,
24
+ "type": [
25
+ "string"
26
+ ]
27
+ },
28
+ "identity": {
29
+ "anyOf": [
30
+ {
31
+ "$ref": "#/definitions/error/definitions/id"
32
+ }
33
+ ]
34
+ }
35
+ },
36
+ "links": [],
37
+ "properties": {
38
+ "id": {
39
+ "$ref": "#/definitions/error/definitions/id"
40
+ },
41
+ "message": {
42
+ "$ref": "#/definitions/error/definitions/message"
43
+ }
44
+ }
45
+ }
46
+ },
47
+ "properties": {
48
+ "error": {
49
+ "$ref": "#/definitions/error"
50
+ }
51
+ }
52
+ }
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Croods
4
+ class Attribute
5
+ attr_accessor :name, :type, :null, :default, :default_function
6
+
7
+ def initialize(name, type, null: nil, default: nil, default_function: nil)
8
+ self.name = name.to_s
9
+ self.type = type
10
+ self.null = null
11
+ self.default = default
12
+ self.default_function = default_function
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'controller/actions'
4
+ require_relative 'controller/authentication'
5
+ require_relative 'controller/authorization'
6
+ require_relative 'controller/multi_tenancy'
7
+ require_relative 'controller/resource'
8
+ require_relative 'controller/model'
9
+ require_relative 'controller/member'
10
+ require_relative 'controller/collection'
11
+ require_relative 'controller/not_found'
12
+ require_relative 'controller/already_taken'
13
+ require_relative 'controller/record_invalid'
14
+
15
+ module Croods
16
+ class Controller < ActionController::API
17
+ include Authentication
18
+ include Authorization
19
+ include MultiTenancy
20
+ include Resource
21
+ include Model
22
+ include Member
23
+ include Collection
24
+ include NotFound
25
+ include AlreadyTaken
26
+ include RecordInvalid
27
+ end
28
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Croods
4
+ class Controller < ActionController::API
5
+ module Actions
6
+ class << self
7
+ def index
8
+ lambda do
9
+ authorize model
10
+
11
+ json = execute_service(collection, params) do
12
+ collection
13
+ end
14
+
15
+ render json: json
16
+ end
17
+ end
18
+
19
+ def show
20
+ lambda do
21
+ authorize member
22
+
23
+ json = execute_service(member, params) do
24
+ member
25
+ end
26
+
27
+ render json: json
28
+ end
29
+ end
30
+
31
+ def create
32
+ lambda do
33
+ member = new_member
34
+
35
+ authorize member
36
+ json = execute_service(member, member_params) do
37
+ member.save!
38
+ member
39
+ end
40
+
41
+ render status: :created, json: json
42
+ end
43
+ end
44
+
45
+ def update
46
+ lambda do
47
+ authorize member
48
+
49
+ json = execute_service(member, member_params) do
50
+ member.update!(member_params)
51
+ member
52
+ end
53
+
54
+ render json: json
55
+ end
56
+ end
57
+
58
+ def destroy
59
+ lambda do
60
+ authorize member
61
+ json = execute_service(member, params) do
62
+ member.destroy!
63
+ end
64
+
65
+ render json: json
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Croods
4
+ class Controller < ActionController::API
5
+ module AlreadyTaken
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ rescue_from ActiveRecord::RecordNotUnique, with: :already_taken
10
+ end
11
+
12
+ protected
13
+
14
+ def already_taken(exception)
15
+ match = exception.message.match(/\((.+)\)=\(.+\) already exists/)
16
+ attribute = match && model.human_attribute_name(match[1])
17
+
18
+ message = attribute ? "#{attribute} already taken" : 'Already taken'
19
+
20
+ render status: :unprocessable_entity, json: {
21
+ id: 'already_taken',
22
+ message: message
23
+ }
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Croods
4
+ class Controller < ActionController::API
5
+ module Authentication
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ before_action :authenticate_user!, unless: :devise_controller?
10
+ end
11
+
12
+ protected
13
+
14
+ def user_params(model)
15
+ return {} unless resource.user_is_the_owner?
16
+ return {} unless model.has_attribute?(:user_id)
17
+
18
+ { user_id: current_user&.id }
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Croods
4
+ class Controller < ActionController::API
5
+ module Authorization
6
+ extend ActiveSupport::Concern
7
+ include Pundit
8
+
9
+ included do
10
+ after_action :verify_authorized, unless: :devise_controller?
11
+ after_action :verify_policy_scoped, unless: :devise_controller?
12
+ rescue_from Pundit::NotAuthorizedError, with: :forbidden
13
+ end
14
+
15
+ def policy_scope(scope)
16
+ @_pundit_policy_scoped = true
17
+ resource.policy_scope(action_name)
18
+ .new(tenant: header_tenant, user: current_user, scope: scope).resolve
19
+ end
20
+
21
+ protected
22
+
23
+ def forbidden(exception)
24
+ render status: :forbidden, json: {
25
+ id: 'forbidden',
26
+ message: exception.message
27
+ }
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Croods
4
+ class Controller < ActionController::API
5
+ module Collection
6
+ protected
7
+
8
+ def collection
9
+ resource
10
+ .apply_filters(policy_scope(model), params)
11
+ .order(resource.sort_by)
12
+ end
13
+ end
14
+ end
15
+ end