panda_pal 5.4.10 → 5.6.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 67af1ecc28a193ad6ff80379c433a273c6b8b85cc809aa2eb1eb6ae636cd0502
4
- data.tar.gz: e02a97fcfa42738a4ed1bafccecee40236c70dd4ba8c84e216b0ed065ec81eba
3
+ metadata.gz: 7993707e0990a86f80f284e71273d15cfe69caf5c66558955fe2b62a9231cc7b
4
+ data.tar.gz: 8023bf4a5aebd34b7525a849114632b97af868874496b831b049873d2e176f96
5
5
  SHA512:
6
- metadata.gz: f84b73988355b2bc76a2f5b2a27baceda59dd2e761f55e4f79b973669c3c272f5581ea4e0640159d6402c1c00bd79974cb1fd4a6753217c647948a3b8e2d609a
7
- data.tar.gz: 2fe8dca8891705940d2f41013ce6a90b88c6043f86f64254ec16e0a8d36fefb4a6b7b3726660a2c1159c0b06f42d8fef227a8fb835837acb6c8c73fc4dddd315
6
+ metadata.gz: d096780e3672ad9883c23f70ad2c339247d65a4cbf217c60f2a2abeae81967dccf834c4181e21b18fb3502b4d2ae7e116469e5f612ae7ce81a77dbbebbbb0eb0
7
+ data.tar.gz: 24aa028f7a8dc64b7884112afa743c710b26ffa3224eac8c968cd75d0cb8665a180b0d0abb5eac6198194a7aabd0d04c71cad680502144c6417223e3e98acd71
data/README.md CHANGED
@@ -21,6 +21,16 @@ PandaPal::stage_navigation(:account_navigation, {
21
21
 
22
22
  Configuration data for an installation can be set by creating a `PandaPal::Organization` record. Due to the nature of the data segregation, once created, the name of the organization should not be changed (and will raise a validation error).
23
23
 
24
+ ### Canvas Installation
25
+ As of version 5.5.0, LTIs can be installed into Canvas via the console:
26
+ ```ruby
27
+ org.install_lti(
28
+ host: "https://your_lti.herokuapp.com",
29
+ context: "account/self", # (Optional) Or "account/3", "course/1", etc
30
+ exists: :error, # (Optional) Action to take if an LTI with the same Key already exists. Options are :error, :replace, :duplicate
31
+ )
32
+ ```
33
+
24
34
  ### LTI 1.3 Configuration
25
35
  LTI 1.3 has some additional configuration steps required to setup an LTI:
26
36
 
@@ -86,6 +96,35 @@ end
86
96
 
87
97
  XML for an installation can be generated by visiting `/lti/config` in your application.
88
98
 
99
+ ### Generated API Methods
100
+ It's common to need to manually trigger logic during UAT. PandaPal 5.6.0+ adds a feature to enable clients to do this themselves.
101
+ This is done by a deployer accessing the console and creating a `PandaPal::ApiCall`. This can be done like so:
102
+ ```ruby
103
+ org.create_api(<<~RUBY, expiration: 30.days.from_now, uses: 10)
104
+ arg1 = p[:some_param] # `p` is an in-scope variable that is a Hash of the params sent to the triggering request.
105
+ PandaPal::Organization.current.trigger_canvas_sync()
106
+ { foo: "bar" } # Will be returned to the client. Can be anything that is JSON serializable.
107
+ RUBY
108
+ # OR
109
+ org.create_api(:symbol_of_method_on_organization, expiration: 30.days.from_now, uses: 10)
110
+ ```
111
+ This will return a URL like such: `/panda_pal/call_logic?some_param=TODO&token=JWT.TOKEN.IS.HERE` that can be either GET'd or POST'd.
112
+ The URL generator will *attempt* to determine which params the logic accepts and insert them as `param=TODO` in the generated URL.
113
+
114
+ When triggered, the return value of the code will be wrapped and sent to the client:
115
+ ```json
116
+ {
117
+ "status": "ok",
118
+ "uses_remaining": 9,
119
+ "result": {
120
+ "foo": "bar",
121
+ }
122
+ }
123
+ ```
124
+
125
+ #### Revoking
126
+ A Call URI may be revoked by deleting the `PandaPal::ApiCall` object. `uses:` and logic are stored in the DB (and can thus be altered), but `expiration:` is stored in the JWT and is thus immutable.
127
+
89
128
  ### Routing
90
129
 
91
130
  The following routes should be added to the routes.rb file of the implementing LTI. (substituting `account_navigation` with the other staged navigation routes, if necessary)
@@ -0,0 +1,36 @@
1
+ require 'jwt'
2
+ require_dependency "panda_pal/application_controller"
3
+
4
+ module PandaPal
5
+ class ApiCallController < ApplicationController
6
+ rescue_from StandardError do |err|
7
+ render json: { status: 'error' }, status: 500
8
+ end
9
+ rescue_from ::JWT::DecodeError do |err|
10
+ render json: { status: 'error', error: "Invalid JWT" }, status: 403
11
+ end
12
+
13
+ around_action do |blk|
14
+ payload = ApiCall.decode_jwt(params[:token])
15
+ org_id = payload['organization_id']
16
+ if org_id
17
+ PandaPal::Organization.find(org_id).switch_tenant do
18
+ blk.call
19
+ end
20
+ else
21
+ blk.call
22
+ end
23
+ end
24
+
25
+ def call
26
+ ac = ApiCall.from_jwt(params.require(:token))
27
+ result = ac.call(params.to_unsafe_h)
28
+
29
+ render json: {
30
+ status: 'ok',
31
+ uses_remaining: ac.uses_remaining,
32
+ result: result,
33
+ }
34
+ end
35
+ end
36
+ end
@@ -1,6 +1,6 @@
1
1
  module PandaPal
2
2
  module LaunchUrlHelpers
3
- def self.absolute_launch_url(launch_type, host:, launch_handler: nil, default_auto_launch: false)
3
+ def self.absolute_launch_url(launch_type, host: uri_host, launch_handler: nil, default_auto_launch: false)
4
4
  opts = PandaPal.lti_paths[launch_type]
5
5
  auto_launch = opts[:auto_launch] != nil ? opts[:auto_launch] : default_auto_launch
6
6
  auto_launch = auto_launch && launch_handler.present?
@@ -18,12 +18,10 @@ module PandaPal
18
18
  end
19
19
 
20
20
  def self.url_for(options)
21
- request = Thread.current[:controller]&.request
22
- host = "#{request.scheme}://#{request.host_with_port}"
23
21
  if options.is_a?(Symbol)
24
- Rails.application.routes.url_helpers.send(options, host: host)
22
+ Rails.application.routes.url_helpers.send(options, host: uri_host)
25
23
  else
26
- options[:host] ||= host
24
+ options[:host] ||= uri_host
27
25
  Rails.application.routes.url_helpers.url_for(options)
28
26
  end
29
27
  end
@@ -74,7 +72,23 @@ module PandaPal
74
72
  uri.to_s
75
73
  end
76
74
 
77
- private
75
+ def self.with_uri_host(uri)
76
+ uri = URI.parse(uri) unless uri.is_a?(URI)
77
+ raise "host: param must have a protocal and no path" if uri.path.present?
78
+
79
+ initial = Thread.current[:panda_pal_access_uri]
80
+ begin
81
+ Thread.current[:panda_pal_access_uri] = uri
82
+ yield
83
+ ensure
84
+ Thread.current[:panda_pal_access_uri] = initial
85
+ end
86
+ end
87
+
88
+ def self.uri_host
89
+ request = Thread.current[:controller]&.request
90
+ Thread.current[:panda_pal_access_uri]&.to_s || "#{request.scheme}://#{request.host_with_port}"
91
+ end
78
92
 
79
93
  def self.resolve_route(key, *arguments, engine: 'PandaPal', **kwargs)
80
94
  return key if key.is_a?(String)
@@ -0,0 +1,58 @@
1
+ require 'jwt'
2
+
3
+ module PandaPal
4
+ class ApiCall < ActiveRecord::Base
5
+ # TODO Add Rate Limiting?
6
+
7
+ def self.decode_jwt(jwt)
8
+ jwt_payload, jwt_header = ::JWT.decode(jwt, Rails.application.secret_key_base, true, { algorithm: 'HS256' })
9
+ jwt_payload
10
+ end
11
+
12
+ def self.from_jwt(jwt)
13
+ jwt = decode_jwt(jwt) if jwt.is_a?(String)
14
+ ApiCall.find(jwt['api_call_id'])
15
+ end
16
+
17
+ before_validation on: [:update] do
18
+ errors.add(:expiration, 'is not changeable') if expiration_changed?
19
+ end
20
+
21
+ def call(params, internal: false)
22
+ if !internal && uses_remaining != nil
23
+ self.uses_remaining -= 1
24
+ if uses_remaining <= 0
25
+ destroy!
26
+ else
27
+ save!
28
+ end
29
+ end
30
+
31
+ prc = eval("->(p) {
32
+ #{logic}
33
+ }")
34
+
35
+ prc.call(params)
36
+ end
37
+
38
+ def call_url(host: nil, params: {}, **kwargs)
39
+ func_params = logic.scan(/\bp\[:(\w+)\]/).flatten
40
+ phash = {}
41
+ func_params.each.with_index do |p, i|
42
+ phash[p.to_sym] = params[p.to_sym] || "TODO"
43
+ end
44
+
45
+ PandaPal::Engine.routes.url_helpers.call_logic_url(self, token: jwt_token(**kwargs), only_path: !host, host: host, **phash, format: nil)
46
+ end
47
+
48
+ def jwt_token(expiration: self.expiration)
49
+ payload = {
50
+ api_call_id: id,
51
+ organization_id: Organization.current&.id,
52
+ }
53
+ payload[:exp] = expiration.iso8601 if expiration.present?
54
+ ::JWT.encode(payload, Rails.application.secret_key_base, 'HS256')
55
+ end
56
+
57
+ end
58
+ end
@@ -1,6 +1,3 @@
1
- require_relative './organization_concerns/settings_validation'
2
- require_relative './organization_concerns/task_scheduling'
3
-
4
1
  module PandaPal
5
2
  module OrganizationConcerns; end
6
3
 
@@ -57,8 +54,63 @@ module PandaPal
57
54
  include ext
58
55
  end
59
56
 
57
+ def create_api(logic, expiration: nil, uses: nil, host: nil)
58
+ switch_tenant do
59
+ logic = "current_organization.#{logic}" if logic.is_a?(Symbol)
60
+ ac = ApiCall.create!(
61
+ logic: logic,
62
+ expiration: expiration,
63
+ uses_remaining: uses,
64
+ )
65
+ ac.call_url(host: host)
66
+ end
67
+ end
68
+
69
+ def lti_platform
70
+ lti_platform_type.new(self)
71
+ end
72
+
73
+ def lti_platform_type
74
+ platform = PandaPal.lti_options[:platform] || 'canvas.instructure.com'
75
+ case platform
76
+ when 'canvas.instructure.com'
77
+ PandaPal::Platform::Canvas
78
+ # when 'bridgeapp.com'
79
+ # TODO Add support for Bridge?
80
+ else
81
+ raise "Unknown platform '#{platform}'"
82
+ end
83
+ end
84
+
85
+ def rename!(new_name)
86
+ do_switch = Apartment::Tenant.current == name
87
+ ActiveRecord::Base.connection.execute(
88
+ "ALTER SCHEMA \"#{name}\" RENAME TO \"#{new_name}\";"
89
+ )
90
+ self.class.where(id: id).update_all(name: new_name)
91
+ reload
92
+ switch_tenant if do_switch
93
+ end
94
+
95
+ def respond_to_missing?(name, include_private = false)
96
+ (platform_org_extension_module&.instance_method(name) rescue nil) || super
97
+ end
98
+
99
+ def method_missing(method, *args, &block)
100
+ ext = platform_org_extension_module
101
+ if (ext.instance_method(method) rescue nil)
102
+ ext.instance_method(method).bind_call(self, *args, &block)
103
+ else
104
+ super
105
+ end
106
+ end
107
+
60
108
  private
61
109
 
110
+ def platform_org_extension_module
111
+ defined?(lti_platform_type::OrgExtension) ? lti_platform_type::OrgExtension : nil
112
+ end
113
+
62
114
  def create_schema
63
115
  Apartment::Tenant.create name
64
116
  end
@@ -13,8 +13,6 @@ unless defined?(Sidekiq.schedule)
13
13
  return
14
14
  end
15
15
 
16
- require_relative 'settings_validation'
17
-
18
16
  module PandaPal
19
17
  module OrganizationConcerns
20
18
  module TaskScheduling
@@ -68,6 +66,10 @@ module PandaPal
68
66
  }
69
67
  end
70
68
 
69
+ def task_scheduled?(name_or_method)
70
+ _schedule_descriptors.key?(name_or_method.to_s)
71
+ end
72
+
71
73
  def remove_scheduled_task(name_or_method)
72
74
  dval = _schedule_descriptors.delete(name_or_method.to_s)
73
75
  Rails.logger.warn("No task with key '#{name_or_method}' to delete!") unless dval.present?
@@ -1,3 +1,8 @@
1
+ begin
2
+ require 'bearcat'
3
+ rescue LoadError
4
+ end
5
+
1
6
  module PandaPal
2
7
  class Platform
3
8
  def public_jwks
@@ -8,13 +13,24 @@ module PandaPal
8
13
  rescue
9
14
  nil
10
15
  end
16
+
17
+ protected
18
+
19
+ def self.find_org_setting(paths, org = current_organization)
20
+ paths.each do |p|
21
+ p = p.split('.').map(&:to_sym)
22
+ val = org.settings.dig(*p)
23
+ return val if val.present?
24
+ end
25
+ nil
26
+ end
11
27
  end
12
28
 
13
29
  class Platform::Canvas < Platform
14
- attr_accessor :base_url
30
+ attr_accessor :organization
15
31
 
16
- def initialize(base_url)
17
- @base_url = base_url
32
+ def initialize(org)
33
+ @organization = org
18
34
  end
19
35
 
20
36
  def host
@@ -32,9 +48,128 @@ module PandaPal
32
48
  def grant_url
33
49
  "#{base_url}/login/oauth2/token"
34
50
  end
35
- end
36
51
 
37
- class Platform
38
- CANVAS = Platform::Canvas.new('https://canvas.instructure.com')
52
+ def base_url; org.canvas_url; end
53
+
54
+ module OrgExtension
55
+ def install_lti(host: nil, context: :root_account, version: 'v1p1', exists: :error)
56
+ raise "Automatically installing this LTI requires Bearcat." unless defined?(Bearcat)
57
+
58
+ context = context.to_s
59
+ context = 'account/self' if context == 'root_account'
60
+ cid, ctype = context.split(/[\.\/]/).reverse
61
+ ctype ||= 'account'
62
+
63
+ existing_installs = bearcat_client.send(:"#{ctype}_external_tools", cid).all_pages_each.filter do |cet|
64
+ cet[:consumer_key] == self.key
65
+ end
66
+
67
+ if existing_installs.present?
68
+ case exists
69
+ when :error
70
+ raise "Tool with key #{self.key} already installed"
71
+ when :duplicate
72
+ when :replace
73
+ existing_installs.each do |install|
74
+ bearcat_client.send(:"delete_#{ctype}_external_tool", cid, install[:id])
75
+ end
76
+ else
77
+ raise "exists: #{exists} is not supported"
78
+ end
79
+ end
80
+
81
+ # TODO LTI 1.3 Support
82
+
83
+ conf = {
84
+ name: PandaPal.lti_options[:title],
85
+ description: PandaPal.lti_options[:description],
86
+ consumer_key: self.key,
87
+ shared_secret: self.secret,
88
+ privacy_level: "public",
89
+ config_type: 'by_url',
90
+ config_url: PandaPal::LaunchUrlHelpers.resolve_route(:v1p0_config_url, host: host),
91
+ }
92
+
93
+ bearcat_client.send(:"create_#{ctype}_external_tool", cid, conf)
94
+ end
95
+
96
+ def lti_api_configuration(host: nil)
97
+ PandaPal::LaunchUrlHelpers.with_uri_host(host) do
98
+ domain = PandaPal.lti_properties[:domain] || host.host
99
+ launch_url = PandaPal.lti_options[:secure_launch_url] ||
100
+ "#{domain}#{PandaPal.lti_options[:secure_launch_path]}" ||
101
+ PandaPal.lti_options[:launch_url] ||
102
+ "#{domain}#{PandaPal.lti_options[:launch_path]}" ||
103
+ domain
104
+
105
+ lti_json = {
106
+ name: PandaPal.lti_options[:title],
107
+ description: PandaPal.lti_options[:description],
108
+ domain: host.host,
109
+ url: launch_url,
110
+ consumer_key: self.key,
111
+ shared_secret: self.secret,
112
+ privacy_level: "public",
113
+
114
+ custom_fields: {},
115
+
116
+ environments: PandaPal.lti_environments,
117
+ }
118
+
119
+ lti_json = lti_json.with_indifferent_access
120
+ lti_json.merge!(PandaPal.lti_properties)
121
+
122
+ (PandaPal.lti_options[:custom_fields] || []).each do |k, v|
123
+ lti_json[:custom_fields][k] = v
124
+ end
125
+
126
+ PandaPal.lti_paths.each do |k, options|
127
+ options = PandaPal::LaunchUrlHelpers.normalize_lti_launch_desc(options)
128
+ options[:url] = PandaPal::LaunchUrlHelpers.absolute_launch_url(
129
+ k.to_sym,
130
+ host: host,
131
+ launch_handler: :v1p0_launch_path,
132
+ default_auto_launch: false
133
+ ).to_s
134
+ lti_json[k] = options
135
+ end
136
+
137
+ lti_json
138
+ end
139
+ end
140
+
141
+ def canvas_url
142
+ PandaPal::Platform.find_org_setting([
143
+ "canvas.base_url",
144
+ "canvas_url",
145
+ "canvas_base_url",
146
+ "canvas.url",
147
+ "base_url",
148
+ ], self) || (Rails.env.development? && 'http://localhost:3000') || 'https://canvas.instructure.com'
149
+ end
150
+
151
+ def canvas_api_token
152
+ PandaPal::Platform.find_org_setting([
153
+ "canvas.api_token",
154
+ "canvas.api_key",
155
+ "canvas.token",
156
+ "canvas_api_token",
157
+ "canvas_token",
158
+ "api_token",
159
+ ], self)
160
+ end
161
+
162
+ if defined?(Bearcat)
163
+ def bearcat_client
164
+ return canvas_sync_client if defined?(canvas_sync_client)
165
+
166
+ Bearcat::Client.new(
167
+ prefix: canvas_url,
168
+ token: canvas_token,
169
+ master_rate_limit: (Rails.env.production? && !!defined?(Redis) && ENV['REDIS_URL'].present?),
170
+ )
171
+ end
172
+ end
173
+ end
39
174
  end
40
175
  end
data/config/routes.rb CHANGED
@@ -1,6 +1,9 @@
1
1
  PandaPal::Engine.routes.draw do
2
2
  get '/config' => 'lti_v1_p0#tool_config' # Legacy Support
3
3
 
4
+ get "/call_logic" => "api_call#call"
5
+ post "/call_logic" => "api_call#call"
6
+
4
7
  scope '/v1p0', as: 'v1p0' do
5
8
  get '/config' => 'lti_v1_p0#tool_config'
6
9
  post '/launch' => 'lti_v1_p0#launch'
@@ -0,0 +1,11 @@
1
+ class CreatePandaPalApiCalls < PandaPal::MiscHelper::MigrationClass
2
+ def change
3
+ create_table :panda_pal_api_calls do |t|
4
+ t.text :logic
5
+ t.string :expiration
6
+ t.integer :uses_remaining
7
+
8
+ t.timestamps null: false
9
+ end
10
+ end
11
+ end
@@ -15,13 +15,11 @@ module PandaPal
15
15
  end
16
16
 
17
17
  initializer :append_migrations do |app|
18
- unless app.root.to_s.match root.to_s
19
- config.paths["db/migrate"].expanded.each do |expanded_path|
20
- app.config.paths["db/migrate"] << expanded_path
21
- end
22
- # Apartment will modify this, but it doesn't fully support engine migrations, so we'll reset it here
23
- ActiveRecord::Migrator.migrations_paths = Rails.application.paths['db/migrate'].to_a
18
+ config.paths["db/migrate"].expanded.each do |expanded_path|
19
+ app.config.paths["db/migrate"] << expanded_path
24
20
  end
21
+ # Apartment will modify this, but it doesn't fully support engine migrations, so we'll reset it here
22
+ ActiveRecord::Migrator.migrations_paths = Rails.application.paths['db/migrate'].to_a
25
23
  end
26
24
 
27
25
  initializer 'interop dependencies' do
@@ -62,7 +60,7 @@ module PandaPal
62
60
  ActionDispatch::Routing::Mapper.send :include, PandaPal::Helpers::RouteHelper
63
61
  end
64
62
 
65
- initializer 'panda_pal.route_options' do |app|
63
+ initializer 'panda_pal.route_options', after: :build_middleware_stack do |app|
66
64
  ActiveSupport.on_load(:action_controller) do
67
65
  Rails.application.reload_routes!
68
66
  PandaPal::validate_pandapal_config!
@@ -70,7 +68,7 @@ module PandaPal
70
68
  end
71
69
 
72
70
  initializer "panda_pal.assets.precompile" do |app|
73
- app.config.assets.precompile << "panda_pal_manifest.js"
71
+ app.config.assets.precompile << "panda_pal_manifest.js" rescue nil
74
72
  end
75
73
 
76
74
  initializer :secure_headers do |app|
@@ -13,14 +13,7 @@ module PandaPal::Helpers
13
13
  end
14
14
 
15
15
  def current_lti_platform
16
- return @current_lti_platform if @current_lti_platform.present?
17
- # TODO: (Future) This could be expanded more to take better advantage of the LTI 1.3 Multi-Tenancy model.
18
- if (canvas_url = current_organization&.settings&.dig(:canvas, :base_url)).present?
19
- @current_lti_platform ||= PandaPal::Platform::Canvas.new(canvas_url)
20
- end
21
- @current_lti_platform ||= PandaPal::Platform::Canvas.new('http://localhost:3000') if Rails.env.development?
22
- @current_lti_platform ||= PandaPal::Platform::CANVAS
23
- @current_lti_platform
16
+ @current_lti_platform ||= current_organization.lti_platform
24
17
  end
25
18
 
26
19
  def lti_launch_params
@@ -41,7 +34,7 @@ module PandaPal::Helpers
41
34
  authorized = false
42
35
  # We should verify the timestamp is recent (within 5 minutes). The approved timestamp is part of the signature,
43
36
  # so we don't need to worry about malicious users messing with it. We should deny requests that come too long
44
- # after the approved timestamp.
37
+ # after the approved timestamp.
45
38
  good_timestamp = params['oauth_timestamp'] && params['oauth_timestamp'].to_i > Time.now.to_i - 300
46
39
  if @organization = good_timestamp && params['oauth_consumer_key'] && PandaPal::Organization.find_by_key(params['oauth_consumer_key'])
47
40
  sanitized_params = request.request_parameters
@@ -1,3 +1,3 @@
1
1
  module PandaPal
2
- VERSION = "5.4.10"
2
+ VERSION = "5.6.1"
3
3
  end
data/panda_pal.gemspec CHANGED
@@ -1,11 +1,16 @@
1
1
  $:.push File.expand_path("../lib", __FILE__)
2
2
 
3
- require "panda_pal/version"
3
+ begin
4
+ require "panda_pal/version"
5
+ version = PandaPal::VERSION
6
+ rescue LoadError
7
+ version = "0.0.0.docker"
8
+ end
4
9
 
5
10
  # Describe your gem and declare its dependencies:
6
11
  Gem::Specification.new do |s|
7
12
  s.name = "panda_pal"
8
- s.version = PandaPal::VERSION
13
+ s.version = version
9
14
  s.authors = ["Instructure ProServe"]
10
15
  s.email = ["pseng@instructure.com"]
11
16
  s.homepage = "http://instructure.com"
@@ -22,10 +27,15 @@ Gem::Specification.new do |s|
22
27
  s.add_dependency 'attr_encrypted', '~> 3.0.0'
23
28
  s.add_dependency 'secure_headers', '~> 6.1'
24
29
  s.add_dependency 'json-jwt'
30
+ s.add_dependency 'jwt'
25
31
  s.add_dependency 'httparty'
26
32
 
33
+ s.add_development_dependency "rails", "~> 5.2"
34
+ s.add_development_dependency 'pg'
27
35
  s.add_development_dependency 'sidekiq'
28
36
  s.add_development_dependency 'sidekiq-scheduler'
37
+ s.add_development_dependency 'ros-apartment', '~> 2.11'
38
+ s.add_development_dependency 'ros-apartment-sidekiq', '~> 1.2'
29
39
  s.add_development_dependency 'rspec-rails'
30
40
  s.add_development_dependency 'factory_girl_rails'
31
41
  end
@@ -0,0 +1,27 @@
1
+ require 'rails_helper'
2
+
3
+ module PandaPal
4
+ RSpec.describe ApiCallController, type: :controller do
5
+ let!(:api_call) { ApiCall.create!(logic: '"#{p[:foo]}_bar"') }
6
+
7
+ def json_body
8
+ JSON.parse(response.body)
9
+ end
10
+
11
+ describe "#call" do
12
+ it "calls the function with parameters" do
13
+ get :call, params: { token: api_call.jwt_token, foo: 'bar', use_route: 'panda_pal' }
14
+ expect(response).to be_successful
15
+ expect(json_body["status"]).to eq 'ok'
16
+ expect(json_body["result"]).to eq 'bar_bar'
17
+ end
18
+
19
+ it "fails given a bad JWT" do
20
+ get :call, params: { token: "bad.jwt.token", foo: 'bar', use_route: 'panda_pal' }
21
+ expect(response).not_to be_successful
22
+ expect(json_body["status"]).to eq 'error'
23
+ expect(json_body["error"]).to eq 'Invalid JWT'
24
+ end
25
+ end
26
+ end
27
+ end
@@ -1,25 +1,25 @@
1
- # SQLite version 3.x
2
- # gem install sqlite3
3
- #
4
- # Ensure the SQLite 3 gem is defined in your Gemfile
5
- # gem 'sqlite3'
6
- #
1
+
7
2
  default: &default
8
3
  adapter: postgresql
9
- pool: 5
10
- timeout: 5000
4
+ encoding: unicode
5
+ # For details on connection pooling, see Rails configuration guide
6
+ # http://guides.rubyonrails.org/configuring.html#database-pooling
7
+ pool: <%= ENV.fetch("DB_POOL_SIZE", nil) || (ENV.fetch("RAILS_MAX_THREADS") { 5 }.to_i + 10) %>
8
+ username: <%= ENV.fetch("DB_USERNAME", "") %>
9
+ password: <%= ENV.fetch("DB_PASSWORD", "") %>
10
+ host: <%= ENV.fetch("DB_ADDRESS", "localhost") %>
11
11
 
12
12
  development:
13
13
  <<: *default
14
14
  database: panda_pal_development
15
15
 
16
- # Warning: The database defined as "test" will be erased and
17
- # re-generated from your development database when you run "rake".
18
- # Do not set this db to the same as development or production.
19
16
  test:
20
17
  <<: *default
21
18
  database: panda_pal_test
22
19
 
23
20
  production:
24
21
  <<: *default
25
- database: db/production.sqlite3
22
+ host: <%= ENV.fetch('DB_ADDRESS', 'localhost') %>
23
+ database: panda_pal_production
24
+ username: <%= ENV.fetch("DB_USERNAME", "panda_pal_specs_postgres_user") %>
25
+ password: <%= ENV.fetch("DB_PASSWORD", 'panda_pal_specs_postgres_password') %>