alpha_api 0.1.0
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.
- checksums.yaml +7 -0
- data/.DS_Store +0 -0
- data/.github/workflows/main.yml +18 -0
- data/.gitignore +8 -0
- data/.rubocop.yml +10 -0
- data/Gemfile +17 -0
- data/Gemfile.lock +100 -0
- data/LICENSE.txt +21 -0
- data/README.md +39 -0
- data/Rakefile +8 -0
- data/alpha_api.gemspec +39 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/lib/alpha_api/application.rb +74 -0
- data/lib/alpha_api/application_settings.rb +35 -0
- data/lib/alpha_api/concerns/actionable.rb +363 -0
- data/lib/alpha_api/dynamic_setting.rb +39 -0
- data/lib/alpha_api/dynamic_settings_node.rb +29 -0
- data/lib/alpha_api/namespace_settings.rb +35 -0
- data/lib/alpha_api/resource_collection.rb +73 -0
- data/lib/alpha_api/serializers/application_record_serializer.rb +13 -0
- data/lib/alpha_api/settings_node.rb +21 -0
- data/lib/alpha_api/version.rb +5 -0
- data/lib/alpha_api.rb +32 -0
- data/lib/generators/install/install_generator.rb +19 -0
- data/lib/generators/install/templates/alpha_api.rb.erb +20 -0
- data/lib/generators/resource/resource_generator.rb +81 -0
- data/lib/generators/resource/templates/controller.rb.erb +49 -0
- data/lib/generators/resource/templates/serializer.rb.erb +6 -0
- metadata +131 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 3a7ebf33759d9ccdcffa80c9fcb6692936b0b2f0cd9231b6b2cc0a7b93d0db04
|
|
4
|
+
data.tar.gz: 5e4265a8e6d4d18037b4a73bd4820760de8af5b4a6d2cdebb204cf209587aaff
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: ed39edc56bf7dfe36028147842a4b35e96993fbb68055a839f5718978be44140e16b8bd9b58debe4be4278d52061e26c5a1445498b6fa93999d1350ad35b4077
|
|
7
|
+
data.tar.gz: ba00720279c4317cb6eb3c0a8b49dddbe52750a84bf5304ab3ad9de9c17f48d144099727c969f03a1c94f45afde3491f35e78d27a33bb51e5a5cfeedc96f8ab8
|
data/.DS_Store
ADDED
|
Binary file
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
name: Ruby
|
|
2
|
+
|
|
3
|
+
on: [push,pull_request]
|
|
4
|
+
|
|
5
|
+
jobs:
|
|
6
|
+
build:
|
|
7
|
+
runs-on: ubuntu-latest
|
|
8
|
+
steps:
|
|
9
|
+
- uses: actions/checkout@v2
|
|
10
|
+
- name: Set up Ruby
|
|
11
|
+
uses: ruby/setup-ruby@v1
|
|
12
|
+
with:
|
|
13
|
+
ruby-version: 2.5.8
|
|
14
|
+
- name: Run the default task
|
|
15
|
+
run: |
|
|
16
|
+
gem install bundler -v 2.2.3
|
|
17
|
+
bundle install
|
|
18
|
+
bundle exec rake
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
data/Gemfile
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
source "https://rubygems.org"
|
|
4
|
+
|
|
5
|
+
# Specify your gem's dependencies in alpha_api.gemspec
|
|
6
|
+
gemspec
|
|
7
|
+
|
|
8
|
+
gem "rake", "~> 13.0"
|
|
9
|
+
|
|
10
|
+
gem "rubocop", "~> 0.80"
|
|
11
|
+
|
|
12
|
+
gem "fast_jsonapi"
|
|
13
|
+
|
|
14
|
+
# For actual pagination
|
|
15
|
+
gem 'kaminari'
|
|
16
|
+
# For rest pagination, using kaminari automatically
|
|
17
|
+
gem 'api-pagination'
|
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
alpha_api (0.1.0)
|
|
5
|
+
activesupport
|
|
6
|
+
api-pagination
|
|
7
|
+
fast_jsonapi
|
|
8
|
+
kaminari
|
|
9
|
+
|
|
10
|
+
GEM
|
|
11
|
+
remote: https://rubygems.org/
|
|
12
|
+
specs:
|
|
13
|
+
actionview (6.1.4.1)
|
|
14
|
+
activesupport (= 6.1.4.1)
|
|
15
|
+
builder (~> 3.1)
|
|
16
|
+
erubi (~> 1.4)
|
|
17
|
+
rails-dom-testing (~> 2.0)
|
|
18
|
+
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
|
19
|
+
activemodel (6.1.4.1)
|
|
20
|
+
activesupport (= 6.1.4.1)
|
|
21
|
+
activerecord (6.1.4.1)
|
|
22
|
+
activemodel (= 6.1.4.1)
|
|
23
|
+
activesupport (= 6.1.4.1)
|
|
24
|
+
activesupport (6.1.4.1)
|
|
25
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
26
|
+
i18n (>= 1.6, < 2)
|
|
27
|
+
minitest (>= 5.1)
|
|
28
|
+
tzinfo (~> 2.0)
|
|
29
|
+
zeitwerk (~> 2.3)
|
|
30
|
+
api-pagination (4.8.2)
|
|
31
|
+
ast (2.4.2)
|
|
32
|
+
builder (3.2.4)
|
|
33
|
+
concurrent-ruby (1.1.9)
|
|
34
|
+
crass (1.0.6)
|
|
35
|
+
erubi (1.10.0)
|
|
36
|
+
fast_jsonapi (1.5)
|
|
37
|
+
activesupport (>= 4.2)
|
|
38
|
+
i18n (1.8.10)
|
|
39
|
+
concurrent-ruby (~> 1.0)
|
|
40
|
+
kaminari (1.2.1)
|
|
41
|
+
activesupport (>= 4.1.0)
|
|
42
|
+
kaminari-actionview (= 1.2.1)
|
|
43
|
+
kaminari-activerecord (= 1.2.1)
|
|
44
|
+
kaminari-core (= 1.2.1)
|
|
45
|
+
kaminari-actionview (1.2.1)
|
|
46
|
+
actionview
|
|
47
|
+
kaminari-core (= 1.2.1)
|
|
48
|
+
kaminari-activerecord (1.2.1)
|
|
49
|
+
activerecord
|
|
50
|
+
kaminari-core (= 1.2.1)
|
|
51
|
+
kaminari-core (1.2.1)
|
|
52
|
+
loofah (2.12.0)
|
|
53
|
+
crass (~> 1.0.2)
|
|
54
|
+
nokogiri (>= 1.5.9)
|
|
55
|
+
minitest (5.14.4)
|
|
56
|
+
nokogiri (1.12.4-x86_64-darwin)
|
|
57
|
+
racc (~> 1.4)
|
|
58
|
+
parallel (1.20.1)
|
|
59
|
+
parser (3.0.2.0)
|
|
60
|
+
ast (~> 2.4.1)
|
|
61
|
+
racc (1.5.2)
|
|
62
|
+
rails-dom-testing (2.0.3)
|
|
63
|
+
activesupport (>= 4.2.0)
|
|
64
|
+
nokogiri (>= 1.6)
|
|
65
|
+
rails-html-sanitizer (1.4.2)
|
|
66
|
+
loofah (~> 2.3)
|
|
67
|
+
rainbow (3.0.0)
|
|
68
|
+
rake (13.0.6)
|
|
69
|
+
regexp_parser (2.1.1)
|
|
70
|
+
rexml (3.2.5)
|
|
71
|
+
rubocop (0.93.1)
|
|
72
|
+
parallel (~> 1.10)
|
|
73
|
+
parser (>= 2.7.1.5)
|
|
74
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
75
|
+
regexp_parser (>= 1.8)
|
|
76
|
+
rexml
|
|
77
|
+
rubocop-ast (>= 0.6.0)
|
|
78
|
+
ruby-progressbar (~> 1.7)
|
|
79
|
+
unicode-display_width (>= 1.4.0, < 2.0)
|
|
80
|
+
rubocop-ast (1.11.0)
|
|
81
|
+
parser (>= 3.0.1.1)
|
|
82
|
+
ruby-progressbar (1.11.0)
|
|
83
|
+
tzinfo (2.0.4)
|
|
84
|
+
concurrent-ruby (~> 1.0)
|
|
85
|
+
unicode-display_width (1.7.0)
|
|
86
|
+
zeitwerk (2.4.2)
|
|
87
|
+
|
|
88
|
+
PLATFORMS
|
|
89
|
+
x86_64-darwin-19
|
|
90
|
+
|
|
91
|
+
DEPENDENCIES
|
|
92
|
+
alpha_api!
|
|
93
|
+
api-pagination
|
|
94
|
+
fast_jsonapi
|
|
95
|
+
kaminari
|
|
96
|
+
rake (~> 13.0)
|
|
97
|
+
rubocop (~> 0.80)
|
|
98
|
+
|
|
99
|
+
BUNDLED WITH
|
|
100
|
+
2.2.3
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 Alba Hoo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# AlphaApi
|
|
2
|
+
|
|
3
|
+
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/alpha_api`. To experiment with that code, run `bin/console` for an interactive prompt.
|
|
4
|
+
|
|
5
|
+
TODO: Delete this and the text above, and describe your gem
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
Add this line to your application's Gemfile:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
gem 'alpha_api'
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
And then execute:
|
|
16
|
+
|
|
17
|
+
$ bundle install
|
|
18
|
+
|
|
19
|
+
Or install it yourself as:
|
|
20
|
+
|
|
21
|
+
$ gem install alpha_api
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
TODO: Write usage instructions here
|
|
26
|
+
|
|
27
|
+
## Development
|
|
28
|
+
|
|
29
|
+
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
30
|
+
|
|
31
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
32
|
+
|
|
33
|
+
## Contributing
|
|
34
|
+
|
|
35
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/alpha_api.
|
|
36
|
+
|
|
37
|
+
## License
|
|
38
|
+
|
|
39
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/alpha_api.gemspec
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lib/alpha_api/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "alpha_api"
|
|
7
|
+
spec.version = AlphaApi::VERSION
|
|
8
|
+
spec.authors = ["Alba Hoo"]
|
|
9
|
+
spec.email = ["alba@tenty.co"]
|
|
10
|
+
|
|
11
|
+
spec.summary = "RESTfulise model with jsonapi"
|
|
12
|
+
spec.description = "Expose models with restful api"
|
|
13
|
+
spec.homepage = "https://github.com/AlbaHoo/AlphaApi"
|
|
14
|
+
spec.license = "MIT"
|
|
15
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
|
|
16
|
+
|
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
18
|
+
spec.metadata["source_code_uri"] = "https://github.com/AlbaHoo/AlphaApi"
|
|
19
|
+
spec.metadata["changelog_uri"] = "https://github.com/AlbaHoo/AlphaApi"
|
|
20
|
+
|
|
21
|
+
# Specify which files should be added to the gem when it is released.
|
|
22
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
23
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
24
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
|
|
25
|
+
end
|
|
26
|
+
spec.bindir = "exe"
|
|
27
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
28
|
+
spec.require_paths = ["lib"]
|
|
29
|
+
|
|
30
|
+
# Uncomment to register a new dependency of your gem
|
|
31
|
+
# spec.add_dependency "example-gem", "~> 1.0"
|
|
32
|
+
spec.add_dependency 'activesupport'
|
|
33
|
+
spec.add_dependency 'fast_jsonapi'
|
|
34
|
+
spec.add_dependency 'kaminari'
|
|
35
|
+
spec.add_dependency 'api-pagination'
|
|
36
|
+
|
|
37
|
+
# For more information and examples about making a new gem, checkout our
|
|
38
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
|
39
|
+
end
|
data/bin/console
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "bundler/setup"
|
|
5
|
+
require "alpha_api"
|
|
6
|
+
|
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
9
|
+
|
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
11
|
+
# require "pry"
|
|
12
|
+
# Pry.start
|
|
13
|
+
|
|
14
|
+
require "irb"
|
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "alpha_api/application_settings"
|
|
3
|
+
require "alpha_api/namespace_settings"
|
|
4
|
+
require 'api-pagination'
|
|
5
|
+
|
|
6
|
+
module AlphaApi
|
|
7
|
+
class Application
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
def setting(name, default)
|
|
11
|
+
ApplicationSettings.register name, default
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def inheritable_setting(name, default)
|
|
15
|
+
NamespaceSettings.register name, default
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def respond_to_missing?(method, include_private = false)
|
|
20
|
+
[settings, namespace_settings].any? { |sets| sets.respond_to?(method) } || super
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def method_missing(method, *args)
|
|
24
|
+
if settings.respond_to?(method)
|
|
25
|
+
settings.send(method, *args)
|
|
26
|
+
elsif namespace_settings.respond_to?(method)
|
|
27
|
+
namespace_settings.send(method, *args)
|
|
28
|
+
else
|
|
29
|
+
super
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def settings
|
|
34
|
+
@settings ||= SettingsNode.build(ApplicationSettings)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def namespace_settings
|
|
38
|
+
@namespace_settings ||= SettingsNode.build(NamespaceSettings)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def initialize
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Runs before the app's initializer
|
|
45
|
+
def before_initializer!
|
|
46
|
+
puts 'before initializer'
|
|
47
|
+
ApiPagination.configure do |config|
|
|
48
|
+
config.page_param do |params|
|
|
49
|
+
if params[:page].is_a?(ActionController::Parameters) && params[:page].include?(:number)
|
|
50
|
+
params[:page][:number]
|
|
51
|
+
else
|
|
52
|
+
1
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
config.per_page_param do |params|
|
|
57
|
+
if params[:page].is_a?(ActionController::Parameters) && params[:page].include?(:size)
|
|
58
|
+
params[:page][:size]
|
|
59
|
+
else
|
|
60
|
+
10
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Runs after the app's initializer
|
|
67
|
+
def after_initializer!
|
|
68
|
+
puts 'after initializer'
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "alpha_api/settings_node"
|
|
3
|
+
|
|
4
|
+
module AlphaApi
|
|
5
|
+
class ApplicationSettings < SettingsNode
|
|
6
|
+
|
|
7
|
+
register :app_path, Rails.root
|
|
8
|
+
|
|
9
|
+
register :api_prefix, 'api/v1'
|
|
10
|
+
|
|
11
|
+
# Load paths for admin configurations. Add folders to this load path
|
|
12
|
+
# to load up other resources for administration. External gems can
|
|
13
|
+
# include their paths in this load path to provide active_admin UIs
|
|
14
|
+
register :load_paths, []
|
|
15
|
+
|
|
16
|
+
# Set default localize format for Date/Time values
|
|
17
|
+
register :localize_format, :long
|
|
18
|
+
|
|
19
|
+
# Alpha Api makes educated guesses when displaying objects, this is
|
|
20
|
+
# the list of methods it tries calling in order
|
|
21
|
+
# Note that Formtastic also has 'collection_label_methods' similar to this
|
|
22
|
+
# used by auto generated dropdowns in filter or belongs_to field of Alpha Api
|
|
23
|
+
register :display_name_methods, [ :display_name,
|
|
24
|
+
:full_name,
|
|
25
|
+
:name,
|
|
26
|
+
:username,
|
|
27
|
+
:login,
|
|
28
|
+
:title,
|
|
29
|
+
:email,
|
|
30
|
+
:to_s ]
|
|
31
|
+
|
|
32
|
+
# Remove sensitive attributes from being displayed, made editable, or exported by default
|
|
33
|
+
register :filter_attributes, [:encrypted_password, :password, :password_confirmation]
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
require "active_support/concern"
|
|
2
|
+
|
|
3
|
+
module AlphaApi
|
|
4
|
+
module Concerns
|
|
5
|
+
module Actionable
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
def create
|
|
9
|
+
authorize! :create, resource_class
|
|
10
|
+
new_resource = build_resource(permitted_create_params)
|
|
11
|
+
if new_resource.valid?
|
|
12
|
+
authorize! :create, new_resource
|
|
13
|
+
new_resource.save
|
|
14
|
+
render status: :created, json: resource_serializer.new(new_resource).serializable_hash
|
|
15
|
+
else
|
|
16
|
+
errors = reformat_validation_error(new_resource)
|
|
17
|
+
raise Exceptions::ValidationErrors.new(errors), 'Validation Errors'
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def index
|
|
22
|
+
authorize! :read, resource_class
|
|
23
|
+
query = apply_filter_and_sort(collection)
|
|
24
|
+
apply_pagination
|
|
25
|
+
if params[:page].present?
|
|
26
|
+
records = paginate(query)
|
|
27
|
+
records = records.padding(params[:page][:offset]) if params[:page][:offset]
|
|
28
|
+
else
|
|
29
|
+
records = query
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
options = options(nested_resources, params[:page], query.count)
|
|
33
|
+
render json: resource_serializer.new(records, options).serializable_hash
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def show
|
|
37
|
+
resource = resource_class.find(params[:id])
|
|
38
|
+
authorize! :read, resource
|
|
39
|
+
options = options(nested_resources)
|
|
40
|
+
render json: resource_serializer.new(resource, options).serializable_hash
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def update
|
|
44
|
+
cached_resource_class = resource_class
|
|
45
|
+
resource = cached_resource_class.find(params[:id])
|
|
46
|
+
authorize! :update, resource
|
|
47
|
+
options = options(nested_resources)
|
|
48
|
+
if resource.update(permitted_update_params(resource))
|
|
49
|
+
updated_resource = cached_resource_class.find(params[:id])
|
|
50
|
+
render json: resource_serializer.new(updated_resource, options).serializable_hash
|
|
51
|
+
else
|
|
52
|
+
errors = reformat_validation_error(resource)
|
|
53
|
+
raise Exceptions::ValidationErrors.new(errors), 'Validation Errors'
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def destroy
|
|
58
|
+
if destroyable
|
|
59
|
+
resource = resource_class.find(params[:id])
|
|
60
|
+
authorize! :destroy, resource
|
|
61
|
+
if resource.destroy
|
|
62
|
+
head :no_content
|
|
63
|
+
else
|
|
64
|
+
raise Exceptions::ValidationErrors.new(resource.errors), 'Validation Errors'
|
|
65
|
+
end
|
|
66
|
+
else
|
|
67
|
+
raise Exceptions::MethodNotAllowed, 'Method Not Allowed'
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
protected
|
|
72
|
+
|
|
73
|
+
def destroyable
|
|
74
|
+
false
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def allowed_associations
|
|
78
|
+
[]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def allowed_sortings
|
|
82
|
+
[]
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def apply_filter_and_sort(query)
|
|
86
|
+
query = apply_standard_filter(query) if fields_filter_required?
|
|
87
|
+
# custom filters
|
|
88
|
+
query = apply_filter(query) if params[:filter]
|
|
89
|
+
query = apply_sorting(query)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# @override customised filters
|
|
93
|
+
def apply_filter(query)
|
|
94
|
+
if filterable_fields.empty?
|
|
95
|
+
raise Exceptions::InvalidFilter, 'Filters are not supported for this resource type'
|
|
96
|
+
else
|
|
97
|
+
query
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def filterable_fields
|
|
102
|
+
[]
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def fields_filter_required?
|
|
106
|
+
(params[:search_term] || params[:filter]) && filterable_fields.present?
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# only override this method when filterable_fields is not empty
|
|
110
|
+
def apply_search_term(query, search_term)
|
|
111
|
+
# exclude all _id fields for OR query
|
|
112
|
+
conditions = filterable_fields.select { |field| field_type(field) == :string }.map do |field|
|
|
113
|
+
%("#{resource_class.table_name}"."#{field}" ILIKE #{sanitise(search_term)})
|
|
114
|
+
end
|
|
115
|
+
query = query.where(conditions.join(' OR '))
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def apply_combined_filters(query)
|
|
119
|
+
conditions = []
|
|
120
|
+
filterable_fields.each do |field|
|
|
121
|
+
value = params.dig(:filter, field)
|
|
122
|
+
type = field_type(field)
|
|
123
|
+
next unless value.present? && type.present?
|
|
124
|
+
if type == :uuid || valid_enum?(field, value)
|
|
125
|
+
query = query.where(field => value)
|
|
126
|
+
elsif type == :string
|
|
127
|
+
query = query.where(%("#{resource_class.table_name}"."#{field}" ILIKE #{sanitise(value)}))
|
|
128
|
+
elsif valid_boolean?(field, value)
|
|
129
|
+
query = query.where(field => value == 'true')
|
|
130
|
+
else
|
|
131
|
+
raise Exceptions::InvalidFilter, 'Only type of string and uuid fields are supported'
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
query
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def apply_standard_filter(query)
|
|
138
|
+
return query if filterable_fields.empty?
|
|
139
|
+
# generate where clauses of _contains
|
|
140
|
+
search_term = params[:search_term]
|
|
141
|
+
query = if search_term.present?
|
|
142
|
+
apply_search_term(query, search_term)
|
|
143
|
+
else
|
|
144
|
+
apply_combined_filters(query)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def apply_pagination
|
|
149
|
+
page_number = (params.dig(:page, :number) || 1).to_i
|
|
150
|
+
page_offset = (params.dig(:page, :offset) || 0).to_i
|
|
151
|
+
page_size = (params.dig(:page, :size) || 20).to_i
|
|
152
|
+
|
|
153
|
+
if allow_all && page_size == -1
|
|
154
|
+
params[:page] = nil
|
|
155
|
+
else
|
|
156
|
+
raise Exceptions::InvalidRequest, 'Page number must be positive' unless page_number.positive?
|
|
157
|
+
raise Exceptions::InvalidRequest, 'Page offset must be non-negative' if page_offset.negative?
|
|
158
|
+
raise Exceptions::InvalidRequest, 'Page size must be positive' unless page_size.positive?
|
|
159
|
+
raise Exceptions::InvalidRequest, 'Page size cannot be greater than 100' if page_size > 100
|
|
160
|
+
|
|
161
|
+
params[:page] = {
|
|
162
|
+
number: page_number,
|
|
163
|
+
offset: page_offset,
|
|
164
|
+
size: page_size
|
|
165
|
+
}
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def allow_all
|
|
170
|
+
false
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def apply_sorting(query)
|
|
174
|
+
sort_params = params['sort']
|
|
175
|
+
return query.order(default_sorting) unless sort_params.present?
|
|
176
|
+
raise Exceptions::InvalidRequest, 'Sort parameter must be a string' unless sort_params.is_a? String
|
|
177
|
+
sorting = []
|
|
178
|
+
|
|
179
|
+
sorts = sort_params.split(',').map(&:strip).map do |sort|
|
|
180
|
+
is_desc = sort.start_with?('-')
|
|
181
|
+
sort = is_desc ? sort[1..-1] : sort
|
|
182
|
+
raise Exceptions::InvalidRequest, "Sorting by #{sort} is not allowed" unless allowed_sortings.include?(sort.to_sym)
|
|
183
|
+
sort = association_mapper(sort)
|
|
184
|
+
|
|
185
|
+
# have to includes the association to be able to sort on
|
|
186
|
+
association = sort.split('.')[-2]
|
|
187
|
+
query = query.includes(association.to_sym) if association
|
|
188
|
+
|
|
189
|
+
sorting << sort_clause(sort, is_desc ? 'DESC NULLS LAST' : 'ASC NULLS FIRST')
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
query.order(sorting.join(','))
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def association_mapper(sort)
|
|
196
|
+
components = sort.split('.')
|
|
197
|
+
return sort if components.length == 1
|
|
198
|
+
mapper = { 'reseller' => 'organisation' }
|
|
199
|
+
table_name = components[-2]
|
|
200
|
+
"#{mapper[table_name] || table_name}.#{components[-1]}"
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def build_resource(resource_params)
|
|
206
|
+
resource_class.new(resource_params)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def collection
|
|
210
|
+
resource_class.accessible_by(current_ability)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def reconcile_nested_attributes(existing_items, items_in_update)
|
|
214
|
+
item_ids_in_update = items_in_update.map { |item| item['id'] }.compact
|
|
215
|
+
if item_ids_in_update.uniq.length != item_ids_in_update.length
|
|
216
|
+
raise(Exceptions::InvalidRequest, 'Nested attribute IDs must be unique')
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
nested_attributes = []
|
|
220
|
+
|
|
221
|
+
items_in_update.each do |item|
|
|
222
|
+
nested_attributes << reconcile_item(existing_items, item)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Existing item was not found in updated items, so should be deleted
|
|
226
|
+
existing_items.reject { |existing| item_ids_in_update.include?(existing.id) }.each do |deleting_item|
|
|
227
|
+
nested_attributes << {
|
|
228
|
+
'id': deleting_item.id,
|
|
229
|
+
'_destroy': true
|
|
230
|
+
}
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
nested_attributes
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def resource
|
|
237
|
+
resource_class.find(params[:id])
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def default_sorting
|
|
241
|
+
{ created_at: :desc }
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def nested_resources
|
|
245
|
+
nested_resources = params[:include].to_s.split(',')
|
|
246
|
+
invalid_resources = []
|
|
247
|
+
nested_resources.each { |res| invalid_resources.push(res) unless allowed_associations.include?(res.to_sym) }
|
|
248
|
+
unless invalid_resources.empty?
|
|
249
|
+
raise Exceptions::InvalidArgument, "Invalid value for include: #{invalid_resources.join(', ')}"
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
nested_resources
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def options(included, page = nil, count = nil)
|
|
256
|
+
options = {
|
|
257
|
+
include: included,
|
|
258
|
+
params: {
|
|
259
|
+
included: included
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
options[:meta] = {}
|
|
263
|
+
options[:meta][:total_count] = count if count
|
|
264
|
+
options[:meta][:page_number] = page[:number] if page
|
|
265
|
+
options[:meta][:page_size] = page[:size] if page
|
|
266
|
+
options
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def permitted_create_params
|
|
270
|
+
data = params.require(:data)
|
|
271
|
+
data.require(:attributes) unless data.include?(:attributes)
|
|
272
|
+
data[:attributes]
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def permitted_update_params(_resource)
|
|
276
|
+
data = params.require(:data)
|
|
277
|
+
data.require(:attributes) unless data.include?(:attributes)
|
|
278
|
+
data[:attributes]
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def reconcile_item(existing_items, item)
|
|
282
|
+
item_id = item['id']
|
|
283
|
+
if item_id && !existing_items.find { |i| i.id == item_id }
|
|
284
|
+
# Any unreconciled items in the update need to be re-created
|
|
285
|
+
item.except(:id)
|
|
286
|
+
else
|
|
287
|
+
item
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def reconcile_nested_attributes(existing_items, items_in_update)
|
|
292
|
+
item_ids_in_update = items_in_update.map { |item| item['id'] }.compact
|
|
293
|
+
if item_ids_in_update.uniq.length != item_ids_in_update.length
|
|
294
|
+
raise(Exceptions::InvalidRequest, 'Nested attribute IDs must be unique')
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
nested_attributes = []
|
|
298
|
+
|
|
299
|
+
items_in_update.each do |item|
|
|
300
|
+
nested_attributes << reconcile_item(existing_items, item)
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# Existing item was not found in updated items, so should be deleted
|
|
304
|
+
existing_items.reject { |existing| item_ids_in_update.include?(existing.id) }.each do |deleting_item|
|
|
305
|
+
nested_attributes << {
|
|
306
|
+
'id': deleting_item.id,
|
|
307
|
+
'_destroy': true
|
|
308
|
+
}
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
nested_attributes
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def reformat_validation_error(resource)
|
|
315
|
+
resource.errors
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
def resource_class
|
|
319
|
+
controller_name.classify.constantize
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def resource_serializer
|
|
323
|
+
"Api::V1::#{controller_name.classify}Serializer".constantize
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
# e.g. sort: 'user.organisation', order: 'desc'
|
|
327
|
+
def sort_clause(sort, order)
|
|
328
|
+
components = sort.split('.')
|
|
329
|
+
attr_name = components[-1]
|
|
330
|
+
if components.length == 1
|
|
331
|
+
# direct attributes
|
|
332
|
+
"#{resource_class.table_name}.#{attr_name} #{order}"
|
|
333
|
+
elsif components.length == 2
|
|
334
|
+
# direct association attributes
|
|
335
|
+
association = resource_class.reflect_on_association(components[-2])
|
|
336
|
+
"#{association.table_name}.#{attr_name} #{order}"
|
|
337
|
+
else
|
|
338
|
+
# could potencially support that as well by includes deeply nested associations
|
|
339
|
+
raise Exceptions::InvalidRequest, 'Sorting on deeply nested association is not supported'
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
private
|
|
344
|
+
|
|
345
|
+
def field_type(field)
|
|
346
|
+
resource_class.attribute_types[field.to_s].type
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def sanitise(str)
|
|
350
|
+
ActiveRecord::Base.connection.quote("%#{str}%")
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def valid_boolean?(field, value)
|
|
354
|
+
field_type(field) == :boolean && ['true', 'false'].include?(value)
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
def valid_enum?(field, value)
|
|
358
|
+
enum = resource_class.defined_enums[field.to_s]
|
|
359
|
+
enum ? enum.keys.include?(value) : false
|
|
360
|
+
end
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module AlphaApi
|
|
3
|
+
|
|
4
|
+
class DynamicSetting
|
|
5
|
+
def self.build(setting, type)
|
|
6
|
+
(type ? klass(type) : self).new(setting)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def self.klass(type)
|
|
10
|
+
klass = "#{type.to_s.camelcase}Setting"
|
|
11
|
+
raise ArgumentError, "Unknown type: #{type}" unless AlphaApi.const_defined?(klass)
|
|
12
|
+
AlphaApi.const_get(klass)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def initialize(setting)
|
|
16
|
+
@setting = setting
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def value(*_args)
|
|
20
|
+
@setting
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Many configuration options (Ex: site_title, title_image) could either be
|
|
25
|
+
# static (String), methods (Symbol) or procs (Proc). This wrapper takes care of
|
|
26
|
+
# returning the content when String or using instance_eval when Symbol or Proc.
|
|
27
|
+
#
|
|
28
|
+
class StringSymbolOrProcSetting < DynamicSetting
|
|
29
|
+
def value(context = self)
|
|
30
|
+
case @setting
|
|
31
|
+
when Symbol, Proc
|
|
32
|
+
context.instance_eval(&@setting)
|
|
33
|
+
else
|
|
34
|
+
@setting
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "alpha_api/dynamic_setting"
|
|
3
|
+
require "alpha_api/settings_node"
|
|
4
|
+
|
|
5
|
+
module AlphaApi
|
|
6
|
+
|
|
7
|
+
class DynamicSettingsNode < SettingsNode
|
|
8
|
+
class << self
|
|
9
|
+
def register(name, value, type = nil)
|
|
10
|
+
class_attribute "#{name}_setting"
|
|
11
|
+
add_reader(name)
|
|
12
|
+
add_writer(name, type)
|
|
13
|
+
send "#{name}=", value
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def add_reader(name)
|
|
17
|
+
define_singleton_method(name) do |*args|
|
|
18
|
+
send("#{name}_setting").value(*args)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def add_writer(name, type)
|
|
23
|
+
define_singleton_method("#{name}=") do |value|
|
|
24
|
+
send("#{name}_setting=", DynamicSetting.build(value, type))
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "alpha_api/dynamic_settings_node"
|
|
3
|
+
|
|
4
|
+
module AlphaApi
|
|
5
|
+
class NamespaceSettings < DynamicSettingsNode
|
|
6
|
+
# The default number of resources to display on index pages
|
|
7
|
+
register :default_per_page, 30
|
|
8
|
+
|
|
9
|
+
# The max number of resources to display on index pages and batch exports
|
|
10
|
+
register :max_per_page, 10_000
|
|
11
|
+
|
|
12
|
+
# The title which gets displayed in the main layout
|
|
13
|
+
register :site_title, "", :string_symbol_or_proc
|
|
14
|
+
|
|
15
|
+
# The method to call in controllers to get the current user
|
|
16
|
+
register :current_user_method, false
|
|
17
|
+
|
|
18
|
+
# The method to call in the controllers to ensure that there
|
|
19
|
+
# is a currently authenticated admin user
|
|
20
|
+
register :authentication_method, false
|
|
21
|
+
|
|
22
|
+
# Whether filters are enabled
|
|
23
|
+
register :filters, true
|
|
24
|
+
|
|
25
|
+
# Request parameters that are permitted by default
|
|
26
|
+
register :permitted_params, [
|
|
27
|
+
:utf8, :_method, :authenticity_token, :commit, :id
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
# Include association filters by default
|
|
31
|
+
register :include_default_association_filters, true
|
|
32
|
+
|
|
33
|
+
register :maximum_association_filter_arity, :unlimited
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module AlphaApi
|
|
3
|
+
# This is a container for resources, which acts much like a Hash.
|
|
4
|
+
# It's assumed that an added resource responds to `resource_name`.
|
|
5
|
+
class ResourceCollection
|
|
6
|
+
include Enumerable
|
|
7
|
+
extend Forwardable
|
|
8
|
+
def_delegators :@collection, :empty?, :has_key?, :keys, :values, :size
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
@collection = {}
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def add(resource)
|
|
15
|
+
if match = @collection[resource.resource_name]
|
|
16
|
+
raise_if_mismatched! match, resource
|
|
17
|
+
match
|
|
18
|
+
else
|
|
19
|
+
@collection[resource.resource_name] = resource
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Changes `each` to pass in the value, instead of both the key and value.
|
|
24
|
+
def each(&block)
|
|
25
|
+
values.each &block
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def [](obj)
|
|
29
|
+
@collection[obj] || find_resource(obj)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
# Finds a resource based on the resource name, resource class, or base class.
|
|
35
|
+
def find_resource(obj)
|
|
36
|
+
resources.detect do |r|
|
|
37
|
+
r.resource_name.to_s == obj.to_s
|
|
38
|
+
end || resources.detect do |r|
|
|
39
|
+
r.resource_class.to_s == obj.to_s
|
|
40
|
+
end ||
|
|
41
|
+
if obj.respond_to? :base_class
|
|
42
|
+
resources.detect { |r| r.resource_class.to_s == obj.base_class.to_s }
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def resources
|
|
47
|
+
select { |r| r.class <= Resource } # can otherwise be a Page
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def raise_if_mismatched!(existing, given)
|
|
51
|
+
if existing.class != given.class
|
|
52
|
+
raise IncorrectClass.new existing, given
|
|
53
|
+
elsif given.class <= Resource && existing.resource_class != given.resource_class
|
|
54
|
+
raise ConfigMismatch.new existing, given
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
class IncorrectClass < StandardError
|
|
59
|
+
def initialize(existing, given)
|
|
60
|
+
super "You're trying to register #{given.resource_name} which is a #{given.class}, " +
|
|
61
|
+
"but #{existing.resource_name}, a #{existing.class} has already claimed that name."
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
class ConfigMismatch < StandardError
|
|
66
|
+
def initialize(existing, given)
|
|
67
|
+
super "You're trying to register #{given.resource_class} as #{given.resource_name}, " +
|
|
68
|
+
"but the existing #{existing.class} config was built for #{existing.resource_class}!"
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
require 'fast_jsonapi'
|
|
2
|
+
|
|
3
|
+
module AlphaApi
|
|
4
|
+
class ApplicationRecordSerializer
|
|
5
|
+
include FastJsonapi::ObjectSerializer
|
|
6
|
+
|
|
7
|
+
class << self
|
|
8
|
+
def requested?(name)
|
|
9
|
+
->(_record, params) { params && params[:included]&.include?(name.to_s) }
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
#
|
|
3
|
+
module AlphaApi
|
|
4
|
+
|
|
5
|
+
class SettingsNode
|
|
6
|
+
class << self
|
|
7
|
+
# Never instantiated. Variables are stored in the singleton_class.
|
|
8
|
+
private_class_method :new
|
|
9
|
+
|
|
10
|
+
# @return anonymous class with same accessors as the superclass.
|
|
11
|
+
def build(superclass = self)
|
|
12
|
+
Class.new(superclass)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def register(name, value)
|
|
16
|
+
class_attribute name
|
|
17
|
+
send "#{name}=", value
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
data/lib/alpha_api.rb
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "alpha_api/version"
|
|
4
|
+
require_relative "alpha_api/application"
|
|
5
|
+
require_relative "generators/resource/resource_generator"
|
|
6
|
+
require_relative "generators/install/install_generator"
|
|
7
|
+
require_relative 'alpha_api/application_settings'
|
|
8
|
+
require_relative 'alpha_api/concerns/actionable'
|
|
9
|
+
require_relative 'alpha_api/serializers/application_record_serializer'
|
|
10
|
+
|
|
11
|
+
module AlphaApi
|
|
12
|
+
class Error < StandardError; end
|
|
13
|
+
# Your code goes here...
|
|
14
|
+
|
|
15
|
+
class << self
|
|
16
|
+
|
|
17
|
+
attr_accessor :application
|
|
18
|
+
|
|
19
|
+
def application
|
|
20
|
+
@application ||= ::AlphaApi::Application.new
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
delegate :register, to: :application
|
|
24
|
+
|
|
25
|
+
# Gets called within the initializer
|
|
26
|
+
def setup
|
|
27
|
+
application.before_initializer!
|
|
28
|
+
yield(application)
|
|
29
|
+
application.after_initializer!
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
#
|
|
3
|
+
module AlphaApi
|
|
4
|
+
module Generators
|
|
5
|
+
class InstallGenerator < Rails::Generators::Base
|
|
6
|
+
desc "Installs AlphaApi"
|
|
7
|
+
|
|
8
|
+
source_root File.expand_path("templates", __dir__)
|
|
9
|
+
|
|
10
|
+
def copy_initializer
|
|
11
|
+
template "alpha_api.rb.erb", "config/initializers/alpha_api.rb"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def something
|
|
15
|
+
puts 'h1'
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
AlphaApi.setup do |config|
|
|
2
|
+
# == Site Title
|
|
3
|
+
#
|
|
4
|
+
# Set the title that is displayed on the main layout
|
|
5
|
+
# for each of the active admin pages.
|
|
6
|
+
#
|
|
7
|
+
config.site_title = "<%= Rails.application.class.name.split("::").first.titlecase %>"
|
|
8
|
+
|
|
9
|
+
# Set the link url for the title. For example, to take
|
|
10
|
+
# users to your main site. Defaults to no link.
|
|
11
|
+
#
|
|
12
|
+
# config.site_title_link = "/"
|
|
13
|
+
|
|
14
|
+
# Set an optional image to be displayed for the header
|
|
15
|
+
# instead of a string (overrides :site_title)
|
|
16
|
+
#
|
|
17
|
+
# Note: Aim for an image that's 21px high so it fits in the header.
|
|
18
|
+
#
|
|
19
|
+
# config.site_title_image = "logo.png"
|
|
20
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AlphaApi
|
|
4
|
+
module Generators
|
|
5
|
+
class ResourceGenerator < Rails::Generators::NamedBase
|
|
6
|
+
desc "Registers resources with AlphaApi"
|
|
7
|
+
|
|
8
|
+
class_option :include_boilerplate, type: :boolean, default: false,
|
|
9
|
+
desc: "Generate boilerplate code for your resource."
|
|
10
|
+
|
|
11
|
+
source_root File.expand_path("templates", __dir__)
|
|
12
|
+
|
|
13
|
+
def generate_controller_file
|
|
14
|
+
prefix = AlphaApi.application.settings.api_prefix
|
|
15
|
+
@boilerplate = Boilerplate.new(class_name, prefix)
|
|
16
|
+
template "controller.rb.erb", "app/controllers/#{prefix}/#{file_path.tr('/', '_').pluralize}_controller.rb"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def generate_serializer_file
|
|
20
|
+
prefix = AlphaApi.application.settings.api_prefix
|
|
21
|
+
@boilerplate = Boilerplate.new(class_name, prefix)
|
|
22
|
+
template "serializer.rb.erb", "app/serializers/#{prefix}/#{file_path.tr('/', '_')}_serializer.rb"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class Boilerplate
|
|
27
|
+
def initialize(class_name, module_path)
|
|
28
|
+
@module_path = module_path
|
|
29
|
+
@class_name = class_name
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def module_name
|
|
33
|
+
@module_path.split('/').map(&:capitalize).join('::')
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def attributes
|
|
37
|
+
@class_name.constantize.new.attributes.keys
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def assignable_attributes
|
|
41
|
+
attributes - %w(id created_at updated_at)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def permit_params
|
|
45
|
+
assignable_attributes.map { |a| a.to_sym.inspect }.join(", ")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def rows
|
|
49
|
+
attributes.map { |a| row(a) }.join("\n ")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def row(name)
|
|
53
|
+
"# row :#{name.gsub(/_id$/, '')}"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def columns
|
|
57
|
+
attributes.map { |a| column(a) }.join("\n ")
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def column(name)
|
|
61
|
+
"# column :#{name.gsub(/_id$/, '')}"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def filters
|
|
65
|
+
attributes.map { |a| filter(a) }.join("\n ")
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def filter(name)
|
|
69
|
+
"# filter :#{name.gsub(/_id$/, '')}"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def form_inputs
|
|
73
|
+
assignable_attributes.map { |a| form_input(a) }.join("\n ")
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def form_input(name)
|
|
77
|
+
"# f.input :#{name.gsub(/_id$/, '')}"
|
|
78
|
+
end
|
|
79
|
+
end#
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
class <%= @boilerplate.module_name %>::<%= class_name.pluralize %>Controller < <%= @boilerplate.module_name %>::BaseController
|
|
2
|
+
include AlphaApi::Concerns::Actionable
|
|
3
|
+
|
|
4
|
+
protected
|
|
5
|
+
|
|
6
|
+
def allow_all
|
|
7
|
+
true
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def allowed_associations
|
|
11
|
+
[:organisation]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def allowed_sortings
|
|
15
|
+
[:email, :name, :role]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def collection
|
|
19
|
+
super.includes(:organisation)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def filterable_fields
|
|
23
|
+
[:email, :name, :role, :organisation_id]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def permitted_create_params
|
|
27
|
+
super.permit(*whitelist)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def permitted_update_params(_resource)
|
|
31
|
+
super.permit(*whitelist)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def whitelist
|
|
35
|
+
[
|
|
36
|
+
:organisation_id,
|
|
37
|
+
:name,
|
|
38
|
+
:email,
|
|
39
|
+
:role,
|
|
40
|
+
:password,
|
|
41
|
+
:password_confirmation
|
|
42
|
+
]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def destroyable
|
|
46
|
+
true
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
metadata
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: alpha_api
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Alba Hoo
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2021-09-12 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: activesupport
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: fast_jsonapi
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: kaminari
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: api-pagination
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
type: :runtime
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0'
|
|
69
|
+
description: Expose models with restful api
|
|
70
|
+
email:
|
|
71
|
+
- alba@tenty.co
|
|
72
|
+
executables: []
|
|
73
|
+
extensions: []
|
|
74
|
+
extra_rdoc_files: []
|
|
75
|
+
files:
|
|
76
|
+
- ".DS_Store"
|
|
77
|
+
- ".github/workflows/main.yml"
|
|
78
|
+
- ".gitignore"
|
|
79
|
+
- ".rubocop.yml"
|
|
80
|
+
- Gemfile
|
|
81
|
+
- Gemfile.lock
|
|
82
|
+
- LICENSE.txt
|
|
83
|
+
- README.md
|
|
84
|
+
- Rakefile
|
|
85
|
+
- alpha_api.gemspec
|
|
86
|
+
- bin/console
|
|
87
|
+
- bin/setup
|
|
88
|
+
- lib/alpha_api.rb
|
|
89
|
+
- lib/alpha_api/application.rb
|
|
90
|
+
- lib/alpha_api/application_settings.rb
|
|
91
|
+
- lib/alpha_api/concerns/actionable.rb
|
|
92
|
+
- lib/alpha_api/dynamic_setting.rb
|
|
93
|
+
- lib/alpha_api/dynamic_settings_node.rb
|
|
94
|
+
- lib/alpha_api/namespace_settings.rb
|
|
95
|
+
- lib/alpha_api/resource_collection.rb
|
|
96
|
+
- lib/alpha_api/serializers/application_record_serializer.rb
|
|
97
|
+
- lib/alpha_api/settings_node.rb
|
|
98
|
+
- lib/alpha_api/version.rb
|
|
99
|
+
- lib/generators/install/install_generator.rb
|
|
100
|
+
- lib/generators/install/templates/alpha_api.rb.erb
|
|
101
|
+
- lib/generators/resource/resource_generator.rb
|
|
102
|
+
- lib/generators/resource/templates/controller.rb.erb
|
|
103
|
+
- lib/generators/resource/templates/serializer.rb.erb
|
|
104
|
+
homepage: https://github.com/AlbaHoo/AlphaApi
|
|
105
|
+
licenses:
|
|
106
|
+
- MIT
|
|
107
|
+
metadata:
|
|
108
|
+
homepage_uri: https://github.com/AlbaHoo/AlphaApi
|
|
109
|
+
source_code_uri: https://github.com/AlbaHoo/AlphaApi
|
|
110
|
+
changelog_uri: https://github.com/AlbaHoo/AlphaApi
|
|
111
|
+
post_install_message:
|
|
112
|
+
rdoc_options: []
|
|
113
|
+
require_paths:
|
|
114
|
+
- lib
|
|
115
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
116
|
+
requirements:
|
|
117
|
+
- - ">="
|
|
118
|
+
- !ruby/object:Gem::Version
|
|
119
|
+
version: 2.3.0
|
|
120
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - ">="
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: '0'
|
|
125
|
+
requirements: []
|
|
126
|
+
rubyforge_project:
|
|
127
|
+
rubygems_version: 2.7.7
|
|
128
|
+
signing_key:
|
|
129
|
+
specification_version: 4
|
|
130
|
+
summary: RESTfulise model with jsonapi
|
|
131
|
+
test_files: []
|