spiffy_stores_app 8.2.6
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/.gitignore +13 -0
- data/.rubocop.yml +7 -0
- data/.travis.yml +9 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +6 -0
- data/README.md +346 -0
- data/Rakefile +6 -0
- data/app/assets/javascripts/spiffy_stores_app/redirect.js +19 -0
- data/app/controllers/spiffy_stores_app/authenticated_controller.rb +11 -0
- data/app/controllers/spiffy_stores_app/sessions_controller.rb +113 -0
- data/app/controllers/spiffy_stores_app/webhooks_controller.rb +36 -0
- data/app/views/spiffy_stores_app/sessions/new.html.erb +123 -0
- data/app/views/spiffy_stores_app/shared/redirect.html.erb +22 -0
- data/config/locales/de.yml +3 -0
- data/config/locales/en.yml +4 -0
- data/config/locales/es.yml +3 -0
- data/config/locales/fr.yml +4 -0
- data/config/locales/ja.yml +3 -0
- data/config/routes.rb +12 -0
- data/docs/Quickstart.md +76 -0
- data/images/app-proxy-screenshot.png +0 -0
- data/lib/generators/spiffy_stores_app/add_after_authenticate_job/add_after_authenticate_job_generator.rb +43 -0
- data/lib/generators/spiffy_stores_app/add_after_authenticate_job/templates/after_authenticate_job.rb +10 -0
- data/lib/generators/spiffy_stores_app/add_webhook/add_webhook_generator.rb +68 -0
- data/lib/generators/spiffy_stores_app/add_webhook/templates/webhook_job.rb +8 -0
- data/lib/generators/spiffy_stores_app/app_proxy_controller/app_proxy_controller_generator.rb +25 -0
- data/lib/generators/spiffy_stores_app/app_proxy_controller/templates/app_proxy_controller.rb +8 -0
- data/lib/generators/spiffy_stores_app/app_proxy_controller/templates/app_proxy_route.rb +10 -0
- data/lib/generators/spiffy_stores_app/app_proxy_controller/templates/index.html.erb +19 -0
- data/lib/generators/spiffy_stores_app/controllers/controllers_generator.rb +29 -0
- data/lib/generators/spiffy_stores_app/home_controller/home_controller_generator.rb +31 -0
- data/lib/generators/spiffy_stores_app/home_controller/templates/home_controller.rb +6 -0
- data/lib/generators/spiffy_stores_app/home_controller/templates/index.html.erb +21 -0
- data/lib/generators/spiffy_stores_app/home_controller/templates/spiffy_stores_app_ready_script.html.erb +7 -0
- data/lib/generators/spiffy_stores_app/install/install_generator.rb +58 -0
- data/lib/generators/spiffy_stores_app/install/templates/_flash_messages.html.erb +19 -0
- data/lib/generators/spiffy_stores_app/install/templates/embedded_app.html.erb +40 -0
- data/lib/generators/spiffy_stores_app/install/templates/omniauth.rb +2 -0
- data/lib/generators/spiffy_stores_app/install/templates/spiffy_provider.rb +11 -0
- data/lib/generators/spiffy_stores_app/install/templates/spiffy_stores_app.rb +9 -0
- data/lib/generators/spiffy_stores_app/routes/routes_generator.rb +31 -0
- data/lib/generators/spiffy_stores_app/routes/templates/routes.rb +11 -0
- data/lib/generators/spiffy_stores_app/shop_model/shop_model_generator.rb +38 -0
- data/lib/generators/spiffy_stores_app/shop_model/templates/db/migrate/create_shops.erb +15 -0
- data/lib/generators/spiffy_stores_app/shop_model/templates/shop.rb +3 -0
- data/lib/generators/spiffy_stores_app/shop_model/templates/shops.yml +3 -0
- data/lib/generators/spiffy_stores_app/spiffy_stores_app_generator.rb +16 -0
- data/lib/generators/spiffy_stores_app/views/views_generator.rb +29 -0
- data/lib/spiffy_stores_app.rb +34 -0
- data/lib/spiffy_stores_app/configuration.rb +72 -0
- data/lib/spiffy_stores_app/controller_concerns/app_proxy_verification.rb +38 -0
- data/lib/spiffy_stores_app/controller_concerns/embedded_app.rb +19 -0
- data/lib/spiffy_stores_app/controller_concerns/localization.rb +22 -0
- data/lib/spiffy_stores_app/controller_concerns/login_protection.rb +103 -0
- data/lib/spiffy_stores_app/controller_concerns/webhook_verification.rb +34 -0
- data/lib/spiffy_stores_app/engine.rb +10 -0
- data/lib/spiffy_stores_app/jobs/scripttags_manager_job.rb +15 -0
- data/lib/spiffy_stores_app/jobs/webhooks_manager_job.rb +15 -0
- data/lib/spiffy_stores_app/managers/scripttags_manager.rb +77 -0
- data/lib/spiffy_stores_app/managers/webhooks_manager.rb +61 -0
- data/lib/spiffy_stores_app/session/in_memory_session_store.rb +27 -0
- data/lib/spiffy_stores_app/session/session_repository.rb +34 -0
- data/lib/spiffy_stores_app/session/session_storage.rb +32 -0
- data/lib/spiffy_stores_app/utils.rb +16 -0
- data/lib/spiffy_stores_app/version.rb +3 -0
- data/spiffy_stores_app.gemspec +26 -0
- metadata +220 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'spiffy_stores_app/version'
|
2
|
+
|
3
|
+
# deps
|
4
|
+
require 'spiffy_stores_api'
|
5
|
+
require 'omniauth-spiffy-oauth2'
|
6
|
+
|
7
|
+
# config
|
8
|
+
require 'spiffy_stores_app/configuration'
|
9
|
+
|
10
|
+
# engine
|
11
|
+
require 'spiffy_stores_app/engine'
|
12
|
+
|
13
|
+
# utils
|
14
|
+
require 'spiffy_stores_app/utils'
|
15
|
+
|
16
|
+
# controller concerns
|
17
|
+
require 'spiffy_stores_app/controller_concerns/localization'
|
18
|
+
require 'spiffy_stores_app/controller_concerns/login_protection'
|
19
|
+
require 'spiffy_stores_app/controller_concerns/embedded_app'
|
20
|
+
require 'spiffy_stores_app/controller_concerns/webhook_verification'
|
21
|
+
require 'spiffy_stores_app/controller_concerns/app_proxy_verification'
|
22
|
+
|
23
|
+
# jobs
|
24
|
+
require 'spiffy_stores_app/jobs/webhooks_manager_job'
|
25
|
+
require 'spiffy_stores_app/jobs/scripttags_manager_job'
|
26
|
+
|
27
|
+
# mangers
|
28
|
+
require 'spiffy_stores_app/managers/webhooks_manager'
|
29
|
+
require 'spiffy_stores_app/managers/scripttags_manager'
|
30
|
+
|
31
|
+
# session
|
32
|
+
require 'spiffy_stores_app/session/session_storage'
|
33
|
+
require 'spiffy_stores_app/session/session_repository'
|
34
|
+
require 'spiffy_stores_app/session/in_memory_session_store'
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module SpiffyStoresApp
|
2
|
+
class Configuration
|
3
|
+
|
4
|
+
# SpiffyStores App settings. These values should match the configuration
|
5
|
+
# for the app in your SpiffyStores Partners page. Change your settings in
|
6
|
+
# `config/initializers/spiffy_stores_app.rb`
|
7
|
+
attr_accessor :application_name
|
8
|
+
attr_accessor :api_key
|
9
|
+
attr_accessor :secret
|
10
|
+
attr_accessor :scope
|
11
|
+
attr_accessor :embedded_app
|
12
|
+
alias_method :embedded_app?, :embedded_app
|
13
|
+
attr_accessor :webhooks
|
14
|
+
attr_accessor :scripttags
|
15
|
+
attr_accessor :after_authenticate_job
|
16
|
+
attr_accessor :session_repository
|
17
|
+
|
18
|
+
# customise urls
|
19
|
+
attr_accessor :root_url
|
20
|
+
|
21
|
+
# customise ActiveJob queue names
|
22
|
+
attr_accessor :scripttags_manager_queue_name
|
23
|
+
attr_accessor :webhooks_manager_queue_name
|
24
|
+
|
25
|
+
# configure spiffystores domain for local spiffy_stores development
|
26
|
+
attr_accessor :spiffy_stores_domain
|
27
|
+
|
28
|
+
# allow namespacing webhook jobs
|
29
|
+
attr_accessor :webhook_jobs_namespace
|
30
|
+
|
31
|
+
def initialize
|
32
|
+
@root_url = '/'
|
33
|
+
@spiffy_stores_domain = 'spiffystores.com'
|
34
|
+
@scripttags_manager_queue_name = Rails.application.config.active_job.queue_name
|
35
|
+
@webhooks_manager_queue_name = Rails.application.config.active_job.queue_name
|
36
|
+
end
|
37
|
+
|
38
|
+
def login_url
|
39
|
+
File.join(@root_url, 'login')
|
40
|
+
end
|
41
|
+
|
42
|
+
def session_repository=(klass)
|
43
|
+
if Rails.configuration.cache_classes
|
44
|
+
SpiffyStoresApp::SessionRepository.storage = klass
|
45
|
+
else
|
46
|
+
ActiveSupport::Reloader.to_prepare do
|
47
|
+
SpiffyStoresApp::SessionRepository.storage = klass
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def has_webhooks?
|
53
|
+
webhooks.present?
|
54
|
+
end
|
55
|
+
|
56
|
+
def has_scripttags?
|
57
|
+
scripttags.present?
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.configuration
|
62
|
+
@configuration ||= Configuration.new
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.configuration=(config)
|
66
|
+
@configuration = config
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.configure
|
70
|
+
yield configuration
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module SpiffyStoresApp
|
2
|
+
module AppProxyVerification
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
skip_before_action :verify_authenticity_token, raise: false
|
7
|
+
before_action :verify_proxy_request
|
8
|
+
end
|
9
|
+
|
10
|
+
def verify_proxy_request
|
11
|
+
return head :forbidden unless query_string_valid?(request.query_string)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def query_string_valid?(query_string)
|
17
|
+
query_hash = Rack::Utils.parse_query(query_string)
|
18
|
+
|
19
|
+
signature = query_hash.delete('signature')
|
20
|
+
return false if signature.nil?
|
21
|
+
|
22
|
+
ActiveSupport::SecurityUtils.secure_compare(
|
23
|
+
calculated_signature(query_hash),
|
24
|
+
signature
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
def calculated_signature(query_hash_without_signature)
|
29
|
+
sorted_params = query_hash_without_signature.collect{|k,v| "#{k}=#{Array(v).join(',')}"}.sort.join
|
30
|
+
|
31
|
+
OpenSSL::HMAC.hexdigest(
|
32
|
+
OpenSSL::Digest.new('sha256'),
|
33
|
+
SpiffyStoresApp.configuration.secret,
|
34
|
+
sorted_params
|
35
|
+
)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module SpiffyStoresApp
|
2
|
+
module EmbeddedApp
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
if SpiffyStoresApp.configuration.embedded_app?
|
7
|
+
after_action :set_esdk_headers
|
8
|
+
layout 'embedded_app'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def set_esdk_headers
|
15
|
+
response.set_header('P3P', 'CP="Not used"')
|
16
|
+
response.headers.except!('X-Frame-Options')
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module SpiffyStoresApp
|
2
|
+
module Localization
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
before_action :set_locale
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def set_locale
|
12
|
+
if params[:locale]
|
13
|
+
session[:locale] = params[:locale]
|
14
|
+
else
|
15
|
+
session[:locale] ||= I18n.default_locale
|
16
|
+
end
|
17
|
+
I18n.locale = session[:locale]
|
18
|
+
rescue I18n::InvalidLocale
|
19
|
+
I18n.locale = I18n.default_locale
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module SpiffyStoresApp
|
2
|
+
module LoginProtection
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
class SpiffyStoresDomainNotFound < StandardError; end
|
6
|
+
|
7
|
+
included do
|
8
|
+
rescue_from ActiveResource::UnauthorizedAccess, :with => :close_session
|
9
|
+
end
|
10
|
+
|
11
|
+
def spiffy_stores_session
|
12
|
+
if shop_session
|
13
|
+
begin
|
14
|
+
SpiffyStoresAPI::Base.activate_session(shop_session)
|
15
|
+
yield
|
16
|
+
ensure
|
17
|
+
SpiffyStoresAPI::Base.clear_session
|
18
|
+
end
|
19
|
+
else
|
20
|
+
redirect_to_login
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def shop_session
|
25
|
+
return unless session[:spiffy_stores]
|
26
|
+
@shop_session ||= SpiffyStoresApp::SessionRepository.retrieve(session[:spiffy_stores])
|
27
|
+
end
|
28
|
+
|
29
|
+
def login_again_if_different_shop
|
30
|
+
if shop_session && params[:store] && params[:store].is_a?(String) && shop_session.url != params[:store]
|
31
|
+
clear_shop_session
|
32
|
+
redirect_to_login
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
def redirect_to_login
|
39
|
+
if request.xhr?
|
40
|
+
head :unauthorized
|
41
|
+
else
|
42
|
+
if request.get?
|
43
|
+
session[:return_to] = "#{request.path}?#{sanitized_params.to_query}"
|
44
|
+
end
|
45
|
+
redirect_to login_url
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def close_session
|
50
|
+
clear_shop_session
|
51
|
+
redirect_to login_url
|
52
|
+
end
|
53
|
+
|
54
|
+
def clear_shop_session
|
55
|
+
session[:spiffy_stores] = nil
|
56
|
+
session[:spiffy_stores_domain] = nil
|
57
|
+
session[:spiffy_stores_user] = nil
|
58
|
+
end
|
59
|
+
|
60
|
+
def login_url
|
61
|
+
url = SpiffyStoresApp.configuration.login_url
|
62
|
+
|
63
|
+
if params[:store].present?
|
64
|
+
query = { store: sanitized_params[:store] }.to_query
|
65
|
+
url = "#{url}?#{query}"
|
66
|
+
end
|
67
|
+
|
68
|
+
url
|
69
|
+
end
|
70
|
+
|
71
|
+
def fullpage_redirect_to(url)
|
72
|
+
if SpiffyStoresApp.configuration.embedded_app?
|
73
|
+
render 'spiffy_stores_app/shared/redirect', layout: false, locals: { url: url, current_spiffy_stores_domain: current_spiffy_stores_domain }
|
74
|
+
else
|
75
|
+
redirect_to url
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def current_spiffy_stores_domain
|
80
|
+
spiffy_stores_domain = sanitized_shop_name || session[:spiffy_stores_domain]
|
81
|
+
return spiffy_stores_domain if spiffy_stores_domain.present?
|
82
|
+
|
83
|
+
raise SpiffyStoresDomainNotFound
|
84
|
+
end
|
85
|
+
|
86
|
+
def sanitized_shop_name
|
87
|
+
@sanitized_shop_name ||= sanitize_shop_param(params)
|
88
|
+
end
|
89
|
+
|
90
|
+
def sanitize_shop_param(params)
|
91
|
+
return unless params[:store].present?
|
92
|
+
SpiffyStoresApp::Utils.sanitize_shop_domain(params[:store])
|
93
|
+
end
|
94
|
+
|
95
|
+
def sanitized_params
|
96
|
+
request.query_parameters.clone.tap do |query_params|
|
97
|
+
if params[:store].is_a?(String)
|
98
|
+
query_params[:store] = sanitize_shop_param(params)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module SpiffyStoresApp
|
2
|
+
module WebhookVerification
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
skip_before_action :verify_authenticity_token, raise: false
|
7
|
+
before_action :verify_request
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def verify_request
|
13
|
+
data = request.raw_post
|
14
|
+
return head :unauthorized unless hmac_valid?(data)
|
15
|
+
end
|
16
|
+
|
17
|
+
def hmac_valid?(data)
|
18
|
+
secret = SpiffyStoresApp.configuration.secret
|
19
|
+
digest = OpenSSL::Digest.new('sha256')
|
20
|
+
ActiveSupport::SecurityUtils.secure_compare(
|
21
|
+
spiffy_stores_hmac,
|
22
|
+
Base64.encode64(OpenSSL::HMAC.digest(digest, secret, data)).strip
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
def shop_domain
|
27
|
+
request.headers['HTTP_X_SPIFFY_STORES_SHOP_DOMAIN']
|
28
|
+
end
|
29
|
+
|
30
|
+
def spiffy_stores_hmac
|
31
|
+
request.headers['HTTP_X_SPIFFY_STORES_HMAC_SHA256']
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module SpiffyStoresApp
|
2
|
+
class Engine < Rails::Engine
|
3
|
+
engine_name 'spiffy_stores_app'
|
4
|
+
isolate_namespace SpiffyStoresApp
|
5
|
+
|
6
|
+
initializer "spiffy_stores_app.assets.precompile" do |app|
|
7
|
+
app.config.assets.precompile += %w( spiffy_stores_app/redirect.js )
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module SpiffyStoresApp
|
2
|
+
class ScripttagsManagerJob < ActiveJob::Base
|
3
|
+
|
4
|
+
queue_as do
|
5
|
+
SpiffyStoresApp.configuration.scripttags_manager_queue_name
|
6
|
+
end
|
7
|
+
|
8
|
+
def perform(shop_domain:, shop_token:, scripttags:)
|
9
|
+
SpiffyStoresAPI::Session.temp(shop_domain, shop_token) do
|
10
|
+
manager = ScripttagsManager.new(scripttags, shop_domain)
|
11
|
+
manager.create_scripttags
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module SpiffyStoresApp
|
2
|
+
class WebhooksManagerJob < ActiveJob::Base
|
3
|
+
|
4
|
+
queue_as do
|
5
|
+
SpiffyStoresApp.configuration.webhooks_manager_queue_name
|
6
|
+
end
|
7
|
+
|
8
|
+
def perform(shop_domain:, shop_token:, webhooks:)
|
9
|
+
SpiffyStoresAPI::Session.temp(shop_domain, shop_token) do
|
10
|
+
manager = WebhooksManager.new(webhooks)
|
11
|
+
manager.create_webhooks
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module SpiffyStoresApp
|
2
|
+
class ScripttagsManager
|
3
|
+
class CreationFailed < StandardError; end
|
4
|
+
|
5
|
+
def self.queue(shop_domain, shop_token, scripttags)
|
6
|
+
SpiffyStoresApp::ScripttagsManagerJob.perform_later(
|
7
|
+
shop_domain: shop_domain,
|
8
|
+
shop_token: shop_token,
|
9
|
+
# Procs cannot be serialized so we interpolate now, if necessary
|
10
|
+
scripttags: build_src(scripttags, shop_domain)
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.build_src(scripttags, domain)
|
15
|
+
scripttags.map do |tag|
|
16
|
+
next tag unless tag[:src].respond_to?(:call)
|
17
|
+
tag = tag.dup
|
18
|
+
tag[:src] = tag[:src].call(domain)
|
19
|
+
tag
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_reader :required_scripttags, :shop_domain
|
24
|
+
|
25
|
+
def initialize(scripttags, shop_domain)
|
26
|
+
@required_scripttags = scripttags
|
27
|
+
@shop_domain = shop_domain
|
28
|
+
end
|
29
|
+
|
30
|
+
def recreate_scripttags!
|
31
|
+
destroy_scripttags
|
32
|
+
create_scripttags
|
33
|
+
end
|
34
|
+
|
35
|
+
def create_scripttags
|
36
|
+
return unless required_scripttags.present?
|
37
|
+
|
38
|
+
expanded_scripttags.each do |scripttag|
|
39
|
+
create_scripttag(scripttag) unless scripttag_exists?(scripttag[:src])
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def destroy_scripttags
|
44
|
+
scripttags = expanded_scripttags
|
45
|
+
SpiffyStoresAPI::ScriptTag.all.each do |tag|
|
46
|
+
SpiffyStoresAPI::ScriptTag.delete(tag.id) if is_required_scripttag?(scripttags, tag)
|
47
|
+
end
|
48
|
+
|
49
|
+
@current_scripttags = nil
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def expanded_scripttags
|
55
|
+
self.class.build_src(required_scripttags, shop_domain)
|
56
|
+
end
|
57
|
+
|
58
|
+
def is_required_scripttag?(scripttags, tag)
|
59
|
+
scripttags.map{ |w| w[:src] }.include? tag.src
|
60
|
+
end
|
61
|
+
|
62
|
+
def create_scripttag(attributes)
|
63
|
+
attributes.reverse_merge!(format: 'json')
|
64
|
+
scripttag = SpiffyStoresAPI::ScriptTag.create(attributes)
|
65
|
+
raise CreationFailed, scripttag.errors.full_messages.to_sentence unless scripttag.persisted?
|
66
|
+
scripttag
|
67
|
+
end
|
68
|
+
|
69
|
+
def scripttag_exists?(src)
|
70
|
+
current_scripttags[src]
|
71
|
+
end
|
72
|
+
|
73
|
+
def current_scripttags
|
74
|
+
@current_scripttags ||= SpiffyStoresAPI::ScriptTag.all.index_by(&:src)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|