crazy_train 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 9196c897891374e79b470c92f2e1d720eeebccc1e7997d05abe0a17ab0dbed59
4
+ data.tar.gz: 46a49259c0d4b009bfcc86dce18829e92b1f9d8604b346db614e29a50e897a9b
5
+ SHA512:
6
+ metadata.gz: 91831f51fc7e5b99e7ab4c498111d5c1a9718c34fe8b0a43a46ead63e2d24bcbaa476ed306643d1c2d4173479e7175b3ce1b9918779abded83c5101d42af8afc
7
+ data.tar.gz: 9b304548763006edf74d56ff0351926c6d24a492b69cc9340632d512fd887193365a3e61242c393b856224e9cff84103405dac27a973aa039fe6bffcda20a06a
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright komagata
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,205 @@
1
+ ![CrazyTrain Logo](./crazy_train.png)
2
+
3
+ *I'm going off the rails on a crazy train.*
4
+
5
+ # CrazyTrain
6
+
7
+ *This gem is still unstable.*
8
+
9
+ Provides a RESTful API for database tables into your rails apps.
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ ```ruby
16
+ gem "crazy_train"
17
+ ```
18
+
19
+ And then execute:
20
+
21
+ ```console
22
+ $ bundle install
23
+ ```
24
+
25
+ Execute the below to generate a configuration file.
26
+
27
+ ```console
28
+ $ rails generate crazy_train:install
29
+ ```
30
+
31
+ ```ruby
32
+ # config/initializers/crazy_train.rb:
33
+ CrazyTrain.setup do |config|
34
+ config.secret = 'xxxxxxxxxxxxxxxx'
35
+ config.unauthorized_role = 'anonymous'
36
+ config.authorized_role = 'authenticated'
37
+ end
38
+ ```
39
+
40
+ Then add a line to routes.rb to mount the API.
41
+
42
+ ```ruby
43
+ # routes.rb:
44
+ Rails.application.routes.draw do
45
+ mount CrazyTrain::Engine, at: '/api'
46
+ end
47
+ ```
48
+
49
+ ## Usage
50
+
51
+ crazy_train uses RLS for access control.
52
+
53
+ If accessed without authentication, it is accessed with `anonymous` ROLE.
54
+ If accessed with authentication, it is accessed with `authenticated` ROLE.
55
+
56
+ Set the authorization and RLS POLICY so that posts can `SELECT` even when unauthorized.
57
+
58
+ (Assume that you have a `posts` table in an existing rails project.)
59
+
60
+ ```sql
61
+ GRANT SELECT ON posts TO anonymous;
62
+ ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
63
+ CREATE POLICY posts_policy ON posts TO anonymous USING (TRUE);
64
+ ```
65
+
66
+ Then you can use the REST API as follows.
67
+
68
+ ```console
69
+ $ curl http://localhost:3000/api/posts
70
+ [
71
+ {
72
+ "id": 1,
73
+ "content": "crazy"
74
+ },
75
+ {
76
+ "id": 2,
77
+ "content": "train"
78
+ }
79
+ ]
80
+ ```
81
+
82
+ You can use the API in the same way if you also set up permissions and POLICY settings for other tables and other ROLEs.
83
+
84
+ Access to the DB in rails is done by `default` ROLE, so it is not affected.
85
+
86
+ For complex items, create a Web API manually as usual, and use this for items that require a simple CRUD.
87
+
88
+ ## Authentication
89
+
90
+ crazy_train provides a mechanism for authentication in JWT.
91
+
92
+ You can generate a token using the `crazy_train:generate_token` task.
93
+ (secret uses the value of `CrazyTrain.config.secret`)
94
+
95
+ ```console
96
+ $ rails crazy_train:generate_token
97
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
98
+ ```
99
+
100
+ The token allows you to access the API in an authenticated state.
101
+
102
+ ```console
103
+ $ curl curl http://localhost:3000/api/posts/1234 \
104
+ -H "Authorization: Bearer aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
105
+ {
106
+ "id": 1234,
107
+ "content": "crazy"
108
+ }
109
+ ```
110
+
111
+ ### JWT-Based User Impersonation
112
+
113
+ What if I want to create another ROLE (e.g. `admin` ROLE) for authentication?
114
+
115
+ In crazy_train, if there is a `role` item in the JWT payload, it will be used as the ROLE name.
116
+
117
+ To create a token to authenticate with admin ROLE, do the following
118
+
119
+ ```console
120
+ $ rails crazy_train:generate_token PAYLOAD='{"role": "admin"}'
121
+ bbbbbbbbbbbbbbbbbbbbbbbbbbb
122
+ ```
123
+
124
+ When the API is accessed using this token, the DB is accessed as an `admin` ROLE.
125
+
126
+ ```console
127
+ $ curl curl http://localhost:3000/api/posts/1234 \
128
+ -H "Authorization: Bearer bbbbbbbbbbbbbbbbbbbbbbbbbbb
129
+ {
130
+ "id": 1234,
131
+ "content": "crazy"
132
+ }
133
+ ```
134
+
135
+ ### Individual user identification
136
+
137
+ What if I need to identify individual users to create a POLICY, such as "I can only view the `posts` I have posted"?
138
+
139
+ In crazy_train, the JWT payload can be retrieved via a user-defined configuration parameter in postgresql.
140
+
141
+ If you want to use the following as a PAYLOAD,
142
+
143
+ ```json
144
+ { "user_id": 1234 }
145
+ ```
146
+
147
+ In the DB, you can use `current_setting` to get it from `request.jwt.claims`.
148
+
149
+ ```sql
150
+ SELECT current_setting('request.jwt.claims', true)::json->>'user_id';
151
+ # => 1234
152
+ ```
153
+
154
+ The POLICY "Only your `posts`` can be viewed" can be written as follows.
155
+
156
+ ```sql
157
+ CREATE POLICY posts_policy ON posts
158
+ USING (user_id = current_setting('request.jwt.claims', true)::json->>'user_id');
159
+ ```
160
+
161
+ ## API
162
+
163
+ Allows CRUD to be performed on tables that exist in the DB.
164
+
165
+ | HTTP Verb | URL | Description |
166
+ | --------- | --- | ----------- |
167
+ | GET | `/api/posts` | List |
168
+ | GET | `/api/posts/1234` | Read |
169
+ | POST | `/api/posts` | Create |
170
+ | PUT | `/api/posts/1234` | Update |
171
+ | Delete | `/api/posts/1234` | Delete |
172
+
173
+ ```sh
174
+ $ curl localhost:3000/api/posts
175
+ [{"id":1,"body":"text 1"},{"id":2,"body":"text 2"},{"id":3,"body":"text 3"}]
176
+ ```
177
+
178
+ ```sh
179
+ $ curl localhost:3000/api/posts/1
180
+ {"id":1,"body":"text 1"}
181
+ ```
182
+
183
+ ### Ordering
184
+
185
+ Sorting can be performed using the `order` parameter. Column names and sort order are separated by `. `, and multiple rules are separated by `,`.
186
+
187
+ ```console
188
+ $ curl http://localhost:3000/api/posts?order=content.asc,id.desc
189
+ [
190
+ {
191
+ "id": 2,
192
+ "content": "train"
193
+ },
194
+ {
195
+ "id": 1,
196
+ "content": "crazy"
197
+ }
198
+ ]
199
+ ```
200
+
201
+ ## Contributing
202
+ Contribution directions go here.
203
+
204
+ ## License
205
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'bundler/setup'
2
+
3
+ APP_RAKEFILE = File.expand_path('test/dummy/Rakefile', __dir__)
4
+ load 'rails/tasks/engine.rake'
5
+
6
+ load 'rails/tasks/statistics.rake'
7
+
8
+ require 'bundler/gem_tasks'
@@ -0,0 +1 @@
1
+ //= link_directory ../stylesheets/crazy_train .css
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,47 @@
1
+ require 'jwt'
2
+
3
+ module CrazyTrain
4
+ class ApplicationController < ActionController::Base
5
+ handle_api_errors
6
+
7
+ before_action :verify_token
8
+ before_action :setup_role
9
+ after_action :teardown_role
10
+
11
+ def verify_token
12
+ @default_role = CrazyTrain.current_role
13
+ @role = if jwt_token && jwt_payload
14
+ jwt_payload['role'] || CrazyTrain.config.authenticated_role
15
+ else
16
+ CrazyTrain.config.unauthorized_role
17
+ end
18
+
19
+ payload_string = JSON.generate(jwt_payload)
20
+ CrazyTrain.setup_jwt_claims!(payload_string)
21
+ end
22
+
23
+ def setup_role
24
+ switch_role(@role)
25
+ end
26
+
27
+ def teardown_role
28
+ switch_role(@default_role)
29
+ end
30
+
31
+ def switch_role(role)
32
+ ActiveRecord::Base.connection.execute("SET ROLE #{role};")
33
+ end
34
+
35
+ def jwt_token
36
+ request.headers['Authorization'].split.last
37
+ rescue StandardError
38
+ nil
39
+ end
40
+
41
+ def jwt_payload
42
+ CrazyTrain::JWT.decode(jwt_token, CrazyTrain.config.secret).first
43
+ rescue StandardError
44
+ nil
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,47 @@
1
+ module CrazyTrain
2
+ class ResourcesController < ApplicationController
3
+ def index
4
+ query_builder = CrazyTrain::QueryBuilder.new(klass, params)
5
+ query_builder.parse!
6
+
7
+ records = query_builder.query
8
+ render json: records
9
+ end
10
+
11
+ def show
12
+ resource = klass.find(params[:id])
13
+ render json: resource
14
+ end
15
+
16
+ def create
17
+ klass.create!(resource_params.to_h)
18
+
19
+ head :created
20
+ end
21
+
22
+ def update
23
+ resource = klass.find(params[:id])
24
+ resource.update!(resource_params.to_h)
25
+
26
+ head :no_content
27
+ end
28
+
29
+ def destroy
30
+ resource = klass.find(params[:id])
31
+ resource.destroy!
32
+
33
+ head :no_content
34
+ end
35
+
36
+ private
37
+
38
+ def klass
39
+ name = params[:resource].singularize
40
+ CrazyTrain::Table.klass(name)
41
+ end
42
+
43
+ def resource_params
44
+ params.permit(*klass.column_names)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,7 @@
1
+ module CrazyTrain
2
+ class TablesController < ApplicationController
3
+ def index
4
+ render json: CrazyTrain::Table.names
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,4 @@
1
+ module CrazyTrain
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module CrazyTrain
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module CrazyTrain
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: 'from@example.com'
4
+ layout 'mailer'
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ module CrazyTrain
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,18 @@
1
+ module CrazyTrain
2
+ class OrderParser
3
+ attr_reader :orders
4
+
5
+ def initialize(order_text)
6
+ @order_text = order_text
7
+ @orders = {}
8
+ end
9
+
10
+ def parse!
11
+ @order_text.split(',').map do |text|
12
+ name, direction = text.split('.')
13
+ direction ||= 'asc'
14
+ @orders[name] = direction
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,26 @@
1
+ module CrazyTrain
2
+ class QueryBuilder
3
+ attr_reader :klass, :params, :query, :orders
4
+
5
+ def initialize(klass, params)
6
+ @klass = klass
7
+ @params = params
8
+ @query = nil
9
+ @orders = []
10
+ end
11
+
12
+ def parse!
13
+ @query = klass.all
14
+
15
+ return unless params[:order]
16
+
17
+ @order_parser = CrazyTrain::OrderParser.new(params[:order])
18
+ @order_parser.parse!
19
+ @orders = @order_parser.orders
20
+ @query = @query.order(@orders)
21
+
22
+ # if params[:select]
23
+ # end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,19 @@
1
+ module CrazyTrain
2
+ class Table
3
+ SYSTEM_TABLES = %w[ar_internal_metadata schema_migrations].freeze
4
+
5
+ class << self
6
+ def names
7
+ ActiveRecord::Base.connection.tables - SYSTEM_TABLES
8
+ end
9
+
10
+ def classes
11
+ names.map { |name| name.classify.constantize }
12
+ end
13
+
14
+ def klass(name)
15
+ name.classify.constantize
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,2 @@
1
+ <h1>Tables#index</h1>
2
+ <p>Find me in app/views/crazy_train/tables/index.html.erb</p>
@@ -0,0 +1,15 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Crazy train</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+
8
+ <%= stylesheet_link_tag "crazy_train/application", media: "all" %>
9
+ </head>
10
+ <body>
11
+
12
+ <%= yield %>
13
+
14
+ </body>
15
+ </html>
data/config/routes.rb ADDED
@@ -0,0 +1,8 @@
1
+ CrazyTrain::Engine.routes.draw do
2
+ get 'tables' => 'tables#index', as: :tables, format: :json
3
+ get ':resource' => 'resources#index', as: :resources, format: :json
4
+ get ':resource/:id' => 'resources#show', as: :resource, format: :json
5
+ post ':resource' => 'resources#create', format: :json
6
+ patch ':resource/:id' => 'resources#update', format: :json
7
+ delete ':resource/:id' => 'resources#destroy', format: :json
8
+ end
@@ -0,0 +1,7 @@
1
+ require 'api_error_handler'
2
+
3
+ module CrazyTrain
4
+ class Engine < ::Rails::Engine
5
+ isolate_namespace CrazyTrain
6
+ end
7
+ end
@@ -0,0 +1,18 @@
1
+ module CrazyTrain
2
+ class Grant
3
+ class << self
4
+ def execute!
5
+ sql "GRANT SELECT ON announcements TO #{CrazyTrain.config.unauthorized_role}"
6
+ sql "GRANT SELECT ON announcements TO #{CrazyTrain.config.authenticated_role}"
7
+ sql 'GRANT ALL PRIVILEGES ON announcements TO admin'
8
+ sql 'GRANT ALL PRIVILEGES ON users TO admin'
9
+ sql 'GRANT ALL PRIVILEGES ON notes TO admin'
10
+ sql 'GRANT USAGE ON announcements_id_seq TO admin'
11
+ end
12
+
13
+ def sql(statement)
14
+ ActiveRecord::Base.connection.execute(statement)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,22 @@
1
+ module CrazyTrain
2
+ class JWT
3
+ HEADERS = { 'alg' => 'HS256', 'typ' => 'JWT' }.freeze
4
+
5
+ def self.encode(payload, secret = CrazyTrain.config.secret)
6
+ ::JWT.encode(payload, secret, 'HS256', HEADERS)
7
+ end
8
+
9
+ def self.decode(token, secret = CrazyTrain.config.secret)
10
+ ::JWT.decode(token, secret, true, HEADERS)
11
+ end
12
+
13
+ def self.generate_token(payload_string)
14
+ payload = JSON.parse(payload_string || '{}')
15
+ encode(payload)
16
+ end
17
+
18
+ def self.generate_jwt_secret
19
+ SecureRandom.alphanumeric(32)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,4 @@
1
+ module CrazyTrain
2
+ class Railtie < ::Rails::Railtie
3
+ end
4
+ end
@@ -0,0 +1,3 @@
1
+ module CrazyTrain
2
+ VERSION = '0.2.0'.freeze
3
+ end
@@ -0,0 +1,39 @@
1
+ require 'crazy_train/version'
2
+ require 'crazy_train/engine'
3
+ require 'crazy_train/jwt'
4
+ require 'jwt'
5
+
6
+ module CrazyTrain
7
+ Config = Struct.new(:secret, :unauthorized_role, :authenticated_role)
8
+ @@config = Config.new(
9
+ secret: 'you-must-change-this',
10
+ unauthorized_role: 'anonymous',
11
+ authenticated_role: 'authenticated'
12
+ )
13
+
14
+ def self.config
15
+ @@config
16
+ end
17
+
18
+ def self.setup
19
+ yield @@config
20
+ end
21
+
22
+ def self.current_role
23
+ ActiveRecord::Base.connection.execute('SELECT current_user').to_a.first['current_user']
24
+ end
25
+
26
+ def self.current_settings
27
+ ActiveRecord::Base.connection.execute('SELECT current_setting').to_a.first['current_setting']
28
+ end
29
+
30
+ def self.setup_jwt_claims!(paylaod)
31
+ ActiveRecord::Base.connection.execute("SET request.jwt.claims = '#{paylaod}'")
32
+ end
33
+
34
+ def self.request_jwt_claims
35
+ sql = "SELECT current_setting('request.jwt.claims', true) AS request_jwt_claims"
36
+ string = ActiveRecord::Base.connection.execute(sql).to_a.first['request_jwt_claims']
37
+ JSON.parse(string)
38
+ end
39
+ end
@@ -0,0 +1,17 @@
1
+ require 'rails/generators'
2
+
3
+ module CrazyTrain
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+ source_root File.expand_path('templates', __dir__)
7
+
8
+ def copy_initializer
9
+ template 'initializer_template.erb', 'config/initializers/crazy_train.rb'
10
+ end
11
+
12
+ def add_route
13
+ route "mount CrazyTrain::Engine, at: '/api'"
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,5 @@
1
+ CrazyTrain.setup do |config|
2
+ config.secret = '<%= CrazyTrain::JWT.generate_jwt_secret %>'
3
+ config.unauthorized_role = 'anonymous'
4
+ config.authorized_role = 'authenticated'
5
+ end
@@ -0,0 +1,7 @@
1
+ require 'crazy_train'
2
+
3
+ namespace :crazy_train do
4
+ task generate_token: :environment do
5
+ puts CrazyTrain::JWT.generate_token(ENV.fetch('PAYLOAD', nil))
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,121 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: crazy_train
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - komagata
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-12-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: api_error_handler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.2.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.2.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: jwt
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.2'
34
+ - - ">="
35
+ - !ruby/object:Gem::Version
36
+ version: 2.2.1
37
+ type: :runtime
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - "~>"
42
+ - !ruby/object:Gem::Version
43
+ version: '2.2'
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 2.2.1
47
+ - !ruby/object:Gem::Dependency
48
+ name: rails
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: 6.0.0
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: 6.0.0
61
+ description: Provides a RESTful API for database tables for your rails apps.
62
+ email:
63
+ - komagata@gmail.com
64
+ executables: []
65
+ extensions: []
66
+ extra_rdoc_files: []
67
+ files:
68
+ - MIT-LICENSE
69
+ - README.md
70
+ - Rakefile
71
+ - app/assets/config/crazy_train_manifest.js
72
+ - app/assets/stylesheets/crazy_train/application.css
73
+ - app/controllers/crazy_train/application_controller.rb
74
+ - app/controllers/crazy_train/resources_controller.rb
75
+ - app/controllers/crazy_train/tables_controller.rb
76
+ - app/helpers/crazy_train/application_helper.rb
77
+ - app/jobs/crazy_train/application_job.rb
78
+ - app/mailers/crazy_train/application_mailer.rb
79
+ - app/models/crazy_train/application_record.rb
80
+ - app/models/crazy_train/order_parser.rb
81
+ - app/models/crazy_train/query_builder.rb
82
+ - app/models/crazy_train/table.rb
83
+ - app/views/crazy_train/tables/index.html.erb
84
+ - app/views/layouts/crazy_train/application.html.erb
85
+ - config/routes.rb
86
+ - lib/crazy_train.rb
87
+ - lib/crazy_train/engine.rb
88
+ - lib/crazy_train/grant.rb
89
+ - lib/crazy_train/jwt.rb
90
+ - lib/crazy_train/railtie.rb
91
+ - lib/crazy_train/version.rb
92
+ - lib/generators/crazy_train/install/install_generator.rb
93
+ - lib/generators/crazy_train/install/templates/initializer_template.erb
94
+ - lib/tasks/crazy_train.rake
95
+ homepage: https://github.com/komagata/crazy_train
96
+ licenses: []
97
+ metadata:
98
+ homepage_uri: https://github.com/komagata/crazy_train
99
+ source_code_uri: https://github.com/komagata/crazy_train
100
+ changelog_uri: https://github.com/komagata/crazy_train/CHANGELOG.md
101
+ rubygems_mfa_required: 'true'
102
+ post_install_message:
103
+ rdoc_options: []
104
+ require_paths:
105
+ - lib
106
+ required_ruby_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: 2.6.0
111
+ required_rubygems_version: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ requirements: []
117
+ rubygems_version: 3.4.22
118
+ signing_key:
119
+ specification_version: 4
120
+ summary: Provides a RESTful API to DB tables.
121
+ test_files: []