app_rail-airtable 0.1.0 → 0.5.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 +4 -4
- data/.gitignore +8 -0
- data/CHANGELOG.md +13 -0
- data/Gemfile +7 -3
- data/Gemfile.lock +33 -36
- data/README.md +63 -15
- data/Rakefile +5 -3
- data/app_rail-airtable.gemspec +26 -15
- data/bin/ara_generator +8 -0
- data/bin/console +4 -3
- data/examples/schemas/daily_logs.json +86 -0
- data/examples/schemas/locations.json +85 -0
- data/examples/schemas/users.json +51 -0
- data/lib/app_rail/airtable/application_record.rb +43 -25
- data/lib/app_rail/airtable/authenticatable.rb +78 -0
- data/lib/app_rail/airtable/authentication_helpers.rb +38 -0
- data/lib/app_rail/airtable/generator.rb +75 -0
- data/lib/app_rail/airtable/sinatra.rb +106 -18
- data/lib/app_rail/airtable/string_ext.rb +7 -0
- data/lib/app_rail/airtable/version.rb +3 -1
- data/lib/app_rail/airtable.rb +9 -3
- data/templates/project/.env.tt +2 -0
- data/templates/project/.gitignore +19 -0
- data/templates/project/Gemfile +24 -0
- data/templates/project/app.json +17 -0
- data/templates/project/boot.rb +8 -0
- data/templates/project/config.ru +6 -0
- data/templates/project/lib/server.rb.tt +68 -0
- data/templates/project/spec/server_spec.rb.tt +128 -0
- data/templates/project/spec/spec_helper.rb +114 -0
- metadata +96 -7
@@ -1,49 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'airrecord'
|
4
|
+
require 'app_rail/steps'
|
5
|
+
require 'active_support/core_ext/string/inflections'
|
2
6
|
|
3
7
|
module AppRail
|
4
8
|
module Airtable
|
5
9
|
class ApplicationRecord < Airrecord::Table
|
6
|
-
|
7
|
-
|
10
|
+
include ActiveSupport::Inflector
|
11
|
+
include AppRail::Steps::Displayable
|
12
|
+
|
13
|
+
def self.base_key
|
14
|
+
ENV.fetch('AIRTABLE_BASE_ID')
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.table_name
|
18
|
+
name.pluralize
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.airtable_attr(*attributes)
|
22
|
+
attributes.each do |attribute|
|
23
|
+
define_method(attribute.to_s.snake_case) { self[attribute] }
|
24
|
+
define_method("#{attribute.to_s.snake_case}=") { |value| self[attribute] = value }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Step utilities
|
29
|
+
def self.ar_list_item(text:, detail_text: nil, image: nil, sf_symbol: nil, material_icon: nil)
|
8
30
|
define_method(:ar_list_item_as_json) do
|
9
31
|
{
|
10
|
-
id:
|
11
|
-
text:
|
12
|
-
detailText:
|
13
|
-
imageURL:
|
14
|
-
sfSymbolName: sf_symbol,
|
15
|
-
materialIconName: material_icon
|
32
|
+
id: id,
|
33
|
+
text: method_value(text).to_s,
|
34
|
+
detailText: method_value(detail_text).to_s,
|
35
|
+
imageURL: method_value(image),
|
36
|
+
sfSymbolName: method_value(sf_symbol),
|
37
|
+
materialIconName: method_value(material_icon)
|
16
38
|
}.compact
|
17
39
|
end
|
18
40
|
end
|
19
|
-
|
41
|
+
|
20
42
|
def self.ar_stack(text_items:)
|
21
43
|
define_method(:ar_stack_as_json) do
|
22
|
-
text_items.map{|ti| {type: :text, label: ti, text: self[ti].to_s} }
|
44
|
+
text_items.map { |ti| { type: :text, label: ti, text: self[ti].to_s } }
|
23
45
|
end
|
24
46
|
end
|
25
|
-
|
47
|
+
|
26
48
|
# Customisable behaviour
|
27
|
-
|
49
|
+
|
28
50
|
# Override to provide custom sorting or filtering for index
|
29
|
-
def self.index
|
51
|
+
def self.index(user:)
|
30
52
|
all
|
31
53
|
end
|
32
|
-
|
54
|
+
|
33
55
|
private
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
respond_to?(name) ? send(name) : self[name]
|
40
|
-
end
|
41
|
-
|
56
|
+
|
57
|
+
def method_value(name)
|
58
|
+
send(name) if name.present?
|
59
|
+
end
|
60
|
+
|
42
61
|
# size is either :small, :large or :full
|
43
62
|
def image(name, index: 0, size: :full)
|
44
|
-
self[name][index][
|
63
|
+
self[name][index]['thumbnails'][size.to_s]['url'] if self[name] && self[name].length > index
|
45
64
|
end
|
46
|
-
|
47
65
|
end
|
48
66
|
end
|
49
|
-
end
|
67
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bcrypt'
|
4
|
+
require 'securerandom'
|
5
|
+
|
6
|
+
module AppRail
|
7
|
+
module Airtable
|
8
|
+
module Authenticatable
|
9
|
+
class AlreadyExistsError < StandardError; end
|
10
|
+
|
11
|
+
include BCrypt
|
12
|
+
|
13
|
+
def self.included(klass)
|
14
|
+
klass.extend(ClassMethods)
|
15
|
+
klass.prepend(InstanceMethods)
|
16
|
+
end
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
def create(email:, password:)
|
20
|
+
raise AlreadyExistsError if find_by_email(email)
|
21
|
+
|
22
|
+
user = new('Email' => email, 'Password Hash' => password_hash(password), 'Access Token' => next_access_token)
|
23
|
+
user.create
|
24
|
+
user
|
25
|
+
end
|
26
|
+
|
27
|
+
def find_by_email_and_password(email, password)
|
28
|
+
user = find_by_email(email)
|
29
|
+
user&.valid_password?(password) ? user : nil
|
30
|
+
end
|
31
|
+
|
32
|
+
def find_by_email(email)
|
33
|
+
all(filter: "{Email} = \"#{email}\"").first
|
34
|
+
end
|
35
|
+
|
36
|
+
def find_by_access_token(access_token)
|
37
|
+
all(filter: "{Access Token} = \"#{access_token}\"").first
|
38
|
+
end
|
39
|
+
|
40
|
+
def password_hash(password)
|
41
|
+
BCrypt::Password.create(password)
|
42
|
+
end
|
43
|
+
|
44
|
+
def next_access_token
|
45
|
+
SecureRandom.hex
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
module InstanceMethods
|
50
|
+
def valid_password?(password)
|
51
|
+
BCrypt::Password.new(password_hash) == password
|
52
|
+
end
|
53
|
+
|
54
|
+
def password_hash
|
55
|
+
self['Password Hash']
|
56
|
+
end
|
57
|
+
|
58
|
+
def oauth_session
|
59
|
+
ensure_access_token!
|
60
|
+
|
61
|
+
{
|
62
|
+
access_token: self['Access Token'],
|
63
|
+
scope: :user,
|
64
|
+
token_type: :bearer,
|
65
|
+
expires_in: 31_536_000 # 1 year
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
def ensure_access_token!
|
70
|
+
unless self['Access Token']
|
71
|
+
self['Access Token'] = User.next_access_token
|
72
|
+
save
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AppRail
|
4
|
+
module Airtable
|
5
|
+
module AuthenticationHelpers
|
6
|
+
def current_user
|
7
|
+
@current_user ||= find_current_user
|
8
|
+
end
|
9
|
+
|
10
|
+
def find_current_user
|
11
|
+
authorization_header && bearer_token ? find_authenticatable_resource(access_token: bearer_token) : nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def bearer_token
|
15
|
+
authorization_values = authorization_header.split(' ')
|
16
|
+
return nil unless authorization_values.count > 1
|
17
|
+
|
18
|
+
authorization_values[1]
|
19
|
+
end
|
20
|
+
|
21
|
+
def authorization_header
|
22
|
+
request.env['HTTP_AUTHORIZATION']
|
23
|
+
end
|
24
|
+
|
25
|
+
def oauth_client_id
|
26
|
+
ENV.fetch('OAUTH_CLIENT_ID')
|
27
|
+
end
|
28
|
+
|
29
|
+
def oauth_client_secret
|
30
|
+
ENV.fetch('OAUTH_CLIENT_SECRET')
|
31
|
+
end
|
32
|
+
|
33
|
+
def authenticate!
|
34
|
+
halt 401 unless current_user
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/core_ext/string/inflections'
|
4
|
+
require 'app_rail/airtable/string_ext'
|
5
|
+
require 'thor'
|
6
|
+
require 'airrecord'
|
7
|
+
|
8
|
+
module AppRail
|
9
|
+
module Airtable
|
10
|
+
class Generator < Thor::Group
|
11
|
+
include Thor::Actions
|
12
|
+
|
13
|
+
argument :output_directory
|
14
|
+
argument :api_key
|
15
|
+
argument :base_id
|
16
|
+
argument :schema, required: false
|
17
|
+
|
18
|
+
def self.source_root
|
19
|
+
File.join(File.dirname(__FILE__), '..', '..', '..')
|
20
|
+
end
|
21
|
+
|
22
|
+
def create_project
|
23
|
+
validate_airtable_schema
|
24
|
+
directory('templates/project', output_directory)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.exit_on_failure?
|
28
|
+
true
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def validate_airtable_schema
|
34
|
+
tables.each do |table|
|
35
|
+
table_name = table['name']
|
36
|
+
table_fields = table['fields'].concat(table['associations']).map { |field| field['name'] }.compact
|
37
|
+
|
38
|
+
airtable_table = find_table(table_name)
|
39
|
+
compare_fields(table_fields, airtable_table)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def tables
|
44
|
+
@tables ||= schema ? JSON.parse(schema)['tables'] : []
|
45
|
+
end
|
46
|
+
|
47
|
+
def find_table(table_name)
|
48
|
+
Airrecord.table(api_key, base_id, table_name)
|
49
|
+
end
|
50
|
+
|
51
|
+
def compare_fields(table_fields, airtable_table)
|
52
|
+
airtable_table.records.first.fields.each_key do |key|
|
53
|
+
next if table_fields.include? key
|
54
|
+
|
55
|
+
raise "ERROR! The key \"#{key}\" is not present in the schema definition."
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def fields_as_params_examples(fields)
|
60
|
+
fields.each_with_object({}) do |field, hsh|
|
61
|
+
hsh[field['name']] = case field['type']
|
62
|
+
when 'date'
|
63
|
+
'01/01/2022'
|
64
|
+
when 'string', 'text'
|
65
|
+
'MyString'
|
66
|
+
when 'integer'
|
67
|
+
10
|
68
|
+
else
|
69
|
+
'MyString'
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -1,46 +1,134 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'sinatra'
|
2
4
|
require 'json'
|
3
5
|
require 'active_support'
|
4
6
|
|
5
|
-
|
7
|
+
# TODO: MBS - move to configure block or other
|
8
|
+
Airrecord.api_key = ENV.fetch('AIRTABLE_API_KEY')
|
6
9
|
|
7
10
|
class Symbol
|
8
|
-
|
9
11
|
def pluralize
|
10
12
|
ActiveSupport::Inflector.pluralize(self)
|
11
13
|
end
|
12
|
-
|
14
|
+
|
15
|
+
def singularize
|
16
|
+
ActiveSupport::Inflector.singularize(self)
|
17
|
+
end
|
18
|
+
|
13
19
|
def classify
|
14
|
-
ActiveSupport::Inflector.classify(self)
|
20
|
+
ActiveSupport::Inflector.classify(self)
|
15
21
|
end
|
16
|
-
|
22
|
+
|
17
23
|
def classify_constantize
|
18
24
|
ActiveSupport::Inflector.constantize(classify)
|
19
25
|
end
|
20
|
-
|
21
26
|
end
|
22
27
|
|
23
28
|
module AppRail
|
24
29
|
module Airtable
|
25
30
|
class Sinatra < Sinatra::Base
|
31
|
+
@@authenticated_route = false
|
32
|
+
|
33
|
+
helpers do
|
34
|
+
def request_body_as_json
|
35
|
+
request.body.rewind
|
36
|
+
JSON.parse(request.body.read)
|
37
|
+
end
|
38
|
+
|
39
|
+
def params_and_body_as_json
|
40
|
+
request_body_as_json.merge(params)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.authenticatable_resources(name, only: %i[index show create update])
|
45
|
+
# If authentication is used then include the correct helpers
|
46
|
+
# Allowing the routes to use `authenticate!` and `current_user`
|
47
|
+
helpers AppRail::Airtable::AuthenticationHelpers
|
48
|
+
|
49
|
+
sign_in_route(name)
|
50
|
+
sign_out_route(name)
|
51
|
+
resources(name, only: only)
|
52
|
+
|
53
|
+
return unless block_given?
|
54
|
+
|
55
|
+
@@authenticated_route = true
|
56
|
+
yield
|
57
|
+
@@authenticated_route = false
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.resources(name, only: %i[index show create update])
|
61
|
+
only = [only] if only.is_a?(Symbol)
|
62
|
+
|
63
|
+
index_route(name, authenticated_route?) if only.include?(:index)
|
64
|
+
show_route(name, authenticated_route?) if only.include?(:show)
|
65
|
+
create_route(name, authenticated_route?) if only.include?(:create)
|
66
|
+
update_route(name, authenticated_route?) if only.include?(:update)
|
67
|
+
end
|
26
68
|
|
27
|
-
def self.
|
28
|
-
|
29
|
-
|
69
|
+
def self.sign_in_route(name)
|
70
|
+
post "/#{name}/session" do
|
71
|
+
halt [401, { error: 'Invalid client_id' }.to_json] unless params['client_id'] == oauth_client_id
|
72
|
+
halt [401, { error: 'Invalid client_secret' }.to_json] unless params['client_secret'] == oauth_client_secret
|
73
|
+
|
74
|
+
resource = name.classify_constantize.authenticate_by_params(params)
|
75
|
+
halt [401, { error: 'Invalid credentials' }.to_json] unless resource
|
76
|
+
|
77
|
+
resource.oauth_session.to_json
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.sign_out_route(name)
|
82
|
+
delete "/#{name}/session" do
|
83
|
+
authenticate!
|
84
|
+
current_user.access_token = nil
|
85
|
+
current_user.save
|
86
|
+
|
87
|
+
# Assume that the client will reload and trigger a 401
|
88
|
+
[].to_json
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.index_route(name, authenticated_route)
|
93
|
+
get "/#{name}" do
|
94
|
+
authenticate! if authenticated_route
|
95
|
+
name.classify_constantize.index(user: authenticated_route ? current_user : nil).map(&:ar_list_item_as_json).to_json
|
30
96
|
end
|
31
|
-
|
32
|
-
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.show_route(name, authenticated_route)
|
100
|
+
get "/#{name}/:id" do
|
101
|
+
authenticate! if authenticated_route
|
33
102
|
name.classify_constantize.find(params['id']).ar_stack_as_json.to_json
|
34
103
|
end
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.create_route(name, authenticated_route)
|
107
|
+
post "/#{name}" do
|
108
|
+
authenticate! if authenticated_route
|
109
|
+
begin
|
110
|
+
as_json = name.classify_constantize.create_as_json(current_user: authenticated_route ? current_user : nil,
|
111
|
+
params: params_and_body_as_json)
|
112
|
+
[201, as_json.to_json]
|
113
|
+
rescue AppRail::Airtable::Authenticatable::AlreadyExistsError
|
114
|
+
[422, { error: "#{name.singularize} already exists" }.to_json]
|
115
|
+
end
|
41
116
|
end
|
42
117
|
end
|
43
118
|
|
119
|
+
def self.update_route(name, authenticated_route)
|
120
|
+
put "/#{name}/:id" do
|
121
|
+
authenticate! if authenticated_route
|
122
|
+
record = name.classify_constantize.find(params['id'])
|
123
|
+
as_json = record.update_as_json(current_user: authenticated_route ? current_user : nil,
|
124
|
+
params: params_and_body_as_json)
|
125
|
+
[200, as_json.to_json]
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def self.authenticated_route?
|
130
|
+
@@authenticated_route
|
131
|
+
end
|
44
132
|
end
|
45
133
|
end
|
46
|
-
end
|
134
|
+
end
|
data/lib/app_rail/airtable.rb
CHANGED
@@ -1,6 +1,12 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'app_rail/airtable/version'
|
4
|
+
require 'app_rail/airtable/string_ext'
|
5
|
+
require 'app_rail/airtable/application_record'
|
6
|
+
require 'app_rail/airtable/authenticatable'
|
7
|
+
require 'app_rail/airtable/authentication_helpers'
|
8
|
+
require 'app_rail/airtable/sinatra'
|
9
|
+
require 'app_rail/airtable/generator'
|
4
10
|
|
5
11
|
module AppRail
|
6
12
|
module Airtable
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
source 'https://rubygems.org'
|
4
|
+
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
|
5
|
+
|
6
|
+
ruby '3.1.2'
|
7
|
+
|
8
|
+
gem 'app_rail-airtable'
|
9
|
+
gem 'app_rail-steps'
|
10
|
+
gem 'puma'
|
11
|
+
gem 'sinatra', '~> 3.0', '>= 3.0.2'
|
12
|
+
|
13
|
+
group :development do
|
14
|
+
gem 'dotenv'
|
15
|
+
end
|
16
|
+
|
17
|
+
group :test do
|
18
|
+
gem 'rack-test'
|
19
|
+
gem 'rspec'
|
20
|
+
end
|
21
|
+
|
22
|
+
group :development, :test do
|
23
|
+
gem 'byebug'
|
24
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
{
|
2
|
+
"name": "App Rail Airtable Service",
|
3
|
+
"description": "A barebones template to convert Airtable data to App Rail format",
|
4
|
+
"buildpacks": [
|
5
|
+
{
|
6
|
+
"url": "heroku/ruby"
|
7
|
+
}
|
8
|
+
],
|
9
|
+
"env": {
|
10
|
+
"AIRTABLE_API_KEY": {
|
11
|
+
"description": "Your Airtable API key."
|
12
|
+
},
|
13
|
+
"AIRTABLE_BASE_ID": {
|
14
|
+
"description": "The ID of the Base you'd like to connect to."
|
15
|
+
}
|
16
|
+
}
|
17
|
+
}
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'app_rail/airtable'
|
4
|
+
require 'active_support/inflector'
|
5
|
+
|
6
|
+
<% @tables.each do |table| -%>
|
7
|
+
<% keys = table['fields'].map { |field| field['name'] if field['type'] != 'association' }.compact -%>
|
8
|
+
class <%= table['name'].singularize %> < AppRail::Airtable::ApplicationRecord
|
9
|
+
<% if table['authenticatable'] == 'True' -%>
|
10
|
+
include AppRail::Airtable::Authenticatable
|
11
|
+
|
12
|
+
<% end -%>
|
13
|
+
airtable_attr <%= keys.map{|f| "\"#{f}\""}.join(", ") %>
|
14
|
+
<% table['ar_class_methods']&.each do |method| -%>
|
15
|
+
<%= method['name'] %> <%= method['properties'].map {|prop| "#{prop['type']}: :#{prop['value']}" }.join(', ') %>
|
16
|
+
<% end -%>
|
17
|
+
|
18
|
+
<% table['associations']&.each do |association| -%>
|
19
|
+
<% word_ending_method = association['type'] == 'has_many' ? :pluralize : :singularize -%>
|
20
|
+
<%= association['type'] %> <%= ":#{association['model'].snake_case.send(word_ending_method)}, class: \"#{association['model'].camelize.singularize}\", column: \"#{association['model'].camelize.send(word_ending_method)}\"" %>
|
21
|
+
<% end -%>
|
22
|
+
<% if table['authenticatable'] == 'True' -%>
|
23
|
+
|
24
|
+
def self.create_as_json(params:, current_user:)
|
25
|
+
record = <%= table['name'].singularize %>.create(email: params["payload"]["email"], password: params["payload"]["password"])
|
26
|
+
{ response: { id: record.id }, oauth_session: record.oauth_session}
|
27
|
+
end
|
28
|
+
|
29
|
+
def update_as_json(params:, current_user:)
|
30
|
+
params['payload'].each { |attr, v| public_send("#{attr}=", v) }
|
31
|
+
save
|
32
|
+
{ response: { id: id }, oauth_session: oauth_session }
|
33
|
+
end
|
34
|
+
<% end -%>
|
35
|
+
end
|
36
|
+
|
37
|
+
<% end -%>
|
38
|
+
|
39
|
+
<% authenticatable_table = @tables.select { |table| table['authenticatable'] == 'True' }&.first -%>
|
40
|
+
<% authenticatable_resource = authenticatable_table['name'] if authenticatable_table -%>
|
41
|
+
class Server < AppRail::Airtable::Sinatra
|
42
|
+
<% if authenticatable_resource -%>
|
43
|
+
<% model_name = authenticatable_resource.singularize -%>
|
44
|
+
helpers do
|
45
|
+
def find_authenticatable_resource(access_token:)
|
46
|
+
<%= model_name %>.find_by_access_token(access_token)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
authenticatable_resources :<%= authenticatable_resource.snake_case %> do
|
51
|
+
<% @tables.each do |table| -%>
|
52
|
+
<% next if table['name'] == authenticatable_resource -%>
|
53
|
+
resources :<%= table['name'].snake_case %>
|
54
|
+
<% end -%>
|
55
|
+
end
|
56
|
+
|
57
|
+
post "/sessions" do
|
58
|
+
oauth_session = <%= model_name %>.create_session_as_json(email: params["username"], password: params["password"])
|
59
|
+
halt 401 unless oauth_session
|
60
|
+
|
61
|
+
oauth_session.to_json
|
62
|
+
end
|
63
|
+
<% else -%>
|
64
|
+
<% @tables.each do |table| -%>
|
65
|
+
resources :<%= table['name'].snake_case %>
|
66
|
+
<% end -%>
|
67
|
+
<% end -%>
|
68
|
+
end
|