spree_gladly 1.0.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/.gitignore +18 -0
- data/.rspec +3 -0
- data/.rubocop.yml +45 -0
- data/.travis.yml +69 -0
- data/Appraisals +63 -0
- data/CODE_OF_CONDUCT.md +128 -0
- data/Gemfile +7 -0
- data/LICENSE +11 -0
- data/README.md +473 -0
- data/Rakefile +23 -0
- data/app/concerns/customer/database_adapter.rb +21 -0
- data/app/controllers/application_controller.rb +2 -0
- data/app/controllers/spree/admin/gladly_settings_controller.rb +38 -0
- data/app/controllers/spree/api/v1/customers_controller.rb +60 -0
- data/app/finders/customer/base_lookup.rb +37 -0
- data/app/finders/customer/basic_lookup.rb +26 -0
- data/app/finders/customer/detailed_lookup.rb +19 -0
- data/app/finders/customer/guest/basic_finder.rb +37 -0
- data/app/finders/customer/guest/detailed_finder.rb +42 -0
- data/app/finders/customer/registered/basic_finder.rb +68 -0
- data/app/finders/customer/registered/detailed_finder.rb +43 -0
- data/app/models/spree_gladly/configuration.rb +25 -0
- data/app/overrides/add_gladly_admin_menu_links.rb +10 -0
- data/app/presenters/customer/address_presenter.rb +27 -0
- data/app/presenters/customer/basic_lookup_presenter.rb +29 -0
- data/app/presenters/customer/detailed_lookup_presenter.rb +30 -0
- data/app/presenters/customer/guest/basic_presenter.rb +53 -0
- data/app/presenters/customer/guest/detailed_presenter.rb +117 -0
- data/app/presenters/customer/registered/basic_presenter.rb +60 -0
- data/app/presenters/customer/registered/detailed_presenter.rb +137 -0
- data/app/services/auth/authorization_header.rb +35 -0
- data/app/services/auth/error.rb +4 -0
- data/app/services/auth/header_parse_error.rb +4 -0
- data/app/services/auth/invalid_signature_error.rb +4 -0
- data/app/services/auth/missing_key_error.rb +4 -0
- data/app/services/auth/request_normalizer.rb +37 -0
- data/app/services/auth/signature_validator.rb +68 -0
- data/app/services/auth/time_header.rb +25 -0
- data/app/validators/lookup_validator.rb +89 -0
- data/app/validators/validation_result.rb +25 -0
- data/app/views/spree/admin/gladly_settings/edit.html.erb +25 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/config/locales/en.yml +25 -0
- data/config/routes.rb +13 -0
- data/gemfiles/spree_3_0.gemfile +16 -0
- data/gemfiles/spree_3_1.gemfile +16 -0
- data/gemfiles/spree_3_7.gemfile +11 -0
- data/gemfiles/spree_4_0.gemfile +11 -0
- data/gemfiles/spree_4_1.gemfile +11 -0
- data/gemfiles/spree_4_2.gemfile +11 -0
- data/gemfiles/spree_master.gemfile +11 -0
- data/lib/generators/spree_gladly/install/install_generator.rb +17 -0
- data/lib/generators/spree_gladly/install/templates/config/initializers/spree_gladly.rb +18 -0
- data/lib/spree_gladly.rb +13 -0
- data/lib/spree_gladly/engine.rb +25 -0
- data/lib/spree_gladly/factories.rb +9 -0
- data/lib/spree_gladly/version.rb +5 -0
- data/spree.png +0 -0
- data/spree_gladly.gemspec +35 -0
- metadata +201 -0
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler'
|
4
|
+
Bundler::GemHelper.install_tasks
|
5
|
+
|
6
|
+
require 'rspec/core/rake_task'
|
7
|
+
require 'spree/testing_support/extension_rake'
|
8
|
+
|
9
|
+
RSpec::Core::RakeTask.new
|
10
|
+
|
11
|
+
task :default do
|
12
|
+
if Dir['spec/dummy'].empty?
|
13
|
+
Rake::Task[:test_app].invoke
|
14
|
+
Dir.chdir('../../')
|
15
|
+
end
|
16
|
+
Rake::Task[:spec].invoke
|
17
|
+
end
|
18
|
+
|
19
|
+
desc 'Generates a dummy app for testing'
|
20
|
+
task :test_app do
|
21
|
+
ENV['LIB_NAME'] = 'spree_gladly'
|
22
|
+
Rake::Task['extension:test_app'].invoke
|
23
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Customer
|
4
|
+
module DatabaseAdapter
|
5
|
+
def concat(*args)
|
6
|
+
if adapter =~ /mysql/i
|
7
|
+
"CONCAT(#{args.join(',')})"
|
8
|
+
else
|
9
|
+
args.join('||')
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def adapter
|
14
|
+
if ActiveRecord::Base.respond_to?(:connection_db_config)
|
15
|
+
ActiveRecord::Base.connection_db_config.configuration_hash[:adapter]
|
16
|
+
else
|
17
|
+
ActiveRecord::Base.connection_config[:adapter]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Spree
|
4
|
+
module Admin
|
5
|
+
class GladlySettingsController < ::Spree::Admin::BaseController
|
6
|
+
NONNEGATIVE_INT_REGEX = /\A[0-9]+\Z/.freeze
|
7
|
+
|
8
|
+
def edit
|
9
|
+
@signing_key = SpreeGladly::Config.signing_key
|
10
|
+
@signing_threshold = SpreeGladly::Config.signing_threshold
|
11
|
+
end
|
12
|
+
|
13
|
+
def update
|
14
|
+
if params[:signing_threshold].present? && params[:signing_threshold] !~ NONNEGATIVE_INT_REGEX
|
15
|
+
flash[:error] = Spree.t('spree_gladly.signing_threshold_error')
|
16
|
+
else
|
17
|
+
set_signing_key
|
18
|
+
set_signing_threshold
|
19
|
+
flash[:success] = Spree.t('spree_gladly.save_success')
|
20
|
+
end
|
21
|
+
|
22
|
+
redirect_to edit_admin_gladly_settings_path
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def set_signing_key
|
28
|
+
SpreeGladly::Config.signing_key = params[:signing_key] if params.key?('signing_key')
|
29
|
+
end
|
30
|
+
|
31
|
+
def set_signing_threshold
|
32
|
+
return unless params.key?(:signing_threshold)
|
33
|
+
|
34
|
+
SpreeGladly::Config.signing_threshold = [0, params[:signing_threshold].to_i].max
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Spree
|
4
|
+
module Api
|
5
|
+
module V1
|
6
|
+
class CustomersController < ::ApplicationController
|
7
|
+
skip_before_action :verify_authenticity_token, only: :lookup
|
8
|
+
before_action :validate_signature, only: :lookup
|
9
|
+
before_action :validate_params, only: :lookup
|
10
|
+
|
11
|
+
rescue_from ::Auth::InvalidSignatureError, with: :authorization_error
|
12
|
+
rescue_from ::Auth::MissingKeyError, with: :authorization_error
|
13
|
+
rescue_from ::Auth::HeaderParseError, with: :authorization_error
|
14
|
+
|
15
|
+
def lookup
|
16
|
+
lookup_level = params['lookupLevel'].downcase.to_sym
|
17
|
+
collection = customer_lookup(type: lookup_level).execute
|
18
|
+
|
19
|
+
render json: serialize_collection(
|
20
|
+
type: lookup_level,
|
21
|
+
collection: collection
|
22
|
+
), status: 200
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def serialize_collection(type:, collection:)
|
28
|
+
presenter = {
|
29
|
+
detailed: SpreeGladly::Config.detailed_lookup_presenter.new(resource: collection),
|
30
|
+
basic: SpreeGladly::Config.basic_lookup_presenter.new(resource: collection)
|
31
|
+
}[type]
|
32
|
+
|
33
|
+
{ results: presenter.to_h }
|
34
|
+
end
|
35
|
+
|
36
|
+
def customer_lookup(type:)
|
37
|
+
{
|
38
|
+
detailed: Customer::DetailedLookup.new(params: params),
|
39
|
+
basic: Customer::BasicLookup.new(params: params)
|
40
|
+
}[type]
|
41
|
+
end
|
42
|
+
|
43
|
+
def validate_signature
|
44
|
+
::Auth::SignatureValidator.new(SpreeGladly::Config.signing_key,
|
45
|
+
SpreeGladly::Config.signing_threshold).validate(request)
|
46
|
+
end
|
47
|
+
|
48
|
+
def authorization_error(error)
|
49
|
+
errors = [{ attr: 'Gladly-Authorization', code: error.class.to_s, detail: error.to_s }]
|
50
|
+
render json: { errors: errors }, status: 401
|
51
|
+
end
|
52
|
+
|
53
|
+
def validate_params
|
54
|
+
result = LookupValidator.new.call(params.permit!.to_h.deep_symbolize_keys)
|
55
|
+
render json: { errors: result.format_errors }, status: 422 unless result.success?
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Customer
|
4
|
+
class BaseLookup
|
5
|
+
include Customer::DatabaseAdapter
|
6
|
+
|
7
|
+
def initialize(params:)
|
8
|
+
@params = params
|
9
|
+
@query = params.include?(:query) ? params.fetch(:query) : {}
|
10
|
+
|
11
|
+
@emails = normalize_param(param: query[:emails])
|
12
|
+
@phones = normalize_param(param: query[:phones])
|
13
|
+
@name = query[:name]
|
14
|
+
@external_customer_id = query[:externalCustomerId]
|
15
|
+
@spree_id = query[:spreeId]
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
attr_reader :params, :query, :emails, :phones, :name, :external_customer_id, :spree_id
|
21
|
+
|
22
|
+
def customer
|
23
|
+
@customer ||= Spree.user_class.where('id = ? OR email = ?', spree_id.to_i, external_customer_id).take
|
24
|
+
end
|
25
|
+
|
26
|
+
def guest_customer?
|
27
|
+
!customer.present?
|
28
|
+
end
|
29
|
+
|
30
|
+
def normalize_param(param:)
|
31
|
+
return [] if param.nil?
|
32
|
+
return param if param.is_a?(Array)
|
33
|
+
|
34
|
+
param.split(',').map(&:strip)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Customer
|
4
|
+
class BasicLookup < Customer::BaseLookup
|
5
|
+
def execute
|
6
|
+
OpenStruct.new(
|
7
|
+
guest_customers: guest_customers(registered_customers.pluck(:email)),
|
8
|
+
registered_customers: registered_customers.uniq.sort
|
9
|
+
)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def guest_customers(excluded_emails)
|
15
|
+
Customer::Guest::BasicFinder.new(emails: emails, options: { excluded_emails: excluded_emails }).execute
|
16
|
+
end
|
17
|
+
|
18
|
+
def registered_customers
|
19
|
+
@registered_customers ||= Customer::Registered::BasicFinder.new(
|
20
|
+
name: name,
|
21
|
+
emails: emails,
|
22
|
+
phones: phones
|
23
|
+
).execute
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Customer
|
4
|
+
class DetailedLookup < Customer::BaseLookup
|
5
|
+
def execute
|
6
|
+
guest_customer? ? guest_customer : registered_customer
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def guest_customer
|
12
|
+
Customer::Guest::DetailedFinder.new(email: external_customer_id).execute
|
13
|
+
end
|
14
|
+
|
15
|
+
def registered_customer
|
16
|
+
Customer::Registered::DetailedFinder.new(customer: customer).execute
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Customer
|
4
|
+
module Guest
|
5
|
+
class BasicFinder
|
6
|
+
def initialize(emails:, options: {})
|
7
|
+
@emails = emails
|
8
|
+
@options = options
|
9
|
+
end
|
10
|
+
|
11
|
+
def execute
|
12
|
+
return [] if emails.empty?
|
13
|
+
|
14
|
+
guest_customers
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
attr_reader :emails, :options
|
20
|
+
|
21
|
+
def guest_customers
|
22
|
+
Spree::Order
|
23
|
+
.where(user_id: nil)
|
24
|
+
.where(email: search_emails)
|
25
|
+
.order(created_at: :desc)
|
26
|
+
.to_a
|
27
|
+
.uniq(&:email)
|
28
|
+
end
|
29
|
+
|
30
|
+
def search_emails
|
31
|
+
return emails - options[:excluded_emails] if options[:excluded_emails].present?
|
32
|
+
|
33
|
+
emails
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Customer
|
4
|
+
module Guest
|
5
|
+
class DetailedFinder
|
6
|
+
def initialize(email:)
|
7
|
+
@email = email
|
8
|
+
end
|
9
|
+
|
10
|
+
def execute
|
11
|
+
OpenStruct.new(customer: customer, transactions: transactions, guest: true)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
attr_reader :email
|
17
|
+
|
18
|
+
def customer
|
19
|
+
transactions.first || []
|
20
|
+
end
|
21
|
+
|
22
|
+
def transactions
|
23
|
+
@transactions ||= find_transactions
|
24
|
+
end
|
25
|
+
|
26
|
+
def find_transactions
|
27
|
+
scope = Spree::Order
|
28
|
+
.includes(SpreeGladly::Config.order_includes)
|
29
|
+
.where(state: SpreeGladly::Config.order_states)
|
30
|
+
.order(SpreeGladly::Config.order_sorting)
|
31
|
+
.where("(#{order_table}.user_id IS NULL AND #{order_table}.email = ?)", email)
|
32
|
+
|
33
|
+
scope = scope.limit(SpreeGladly::Config.order_limit) if SpreeGladly::Config.order_limit
|
34
|
+
scope.to_a
|
35
|
+
end
|
36
|
+
|
37
|
+
def order_table
|
38
|
+
Spree::Order.table_name
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Customer
|
4
|
+
module Registered
|
5
|
+
class BasicFinder
|
6
|
+
include Customer::DatabaseAdapter
|
7
|
+
|
8
|
+
def initialize(name:, emails:, phones:)
|
9
|
+
@name = name
|
10
|
+
@emails = emails
|
11
|
+
@phones = phones
|
12
|
+
end
|
13
|
+
|
14
|
+
def execute
|
15
|
+
registered_customers
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
attr_reader :name, :emails, :phones
|
21
|
+
|
22
|
+
def registered_customers
|
23
|
+
conditions = search_conditions
|
24
|
+
return empty_scope unless conditions.present?
|
25
|
+
|
26
|
+
template = conditions.map(&:first).join(' OR ')
|
27
|
+
args = conditions.map(&:last)
|
28
|
+
|
29
|
+
scope.where(template, *args)
|
30
|
+
end
|
31
|
+
|
32
|
+
def search_conditions
|
33
|
+
[by_email, by_name, by_phone].compact
|
34
|
+
end
|
35
|
+
|
36
|
+
def by_email
|
37
|
+
return nil unless emails.present?
|
38
|
+
|
39
|
+
where = "#{Spree.user_class.table_name}.email IN (?)"
|
40
|
+
[where, emails]
|
41
|
+
end
|
42
|
+
|
43
|
+
def by_name
|
44
|
+
return nil unless name.present?
|
45
|
+
|
46
|
+
sql_name = concat("#{Spree::Address.table_name}.firstname", "' '", "#{Spree::Address.table_name}.lastname")
|
47
|
+
where = "(LOWER(#{sql_name}) LIKE ?)"
|
48
|
+
args = "%#{name.downcase}%"
|
49
|
+
[where, args]
|
50
|
+
end
|
51
|
+
|
52
|
+
def by_phone
|
53
|
+
return nil unless phones.present?
|
54
|
+
|
55
|
+
where = "#{Spree::Address.table_name}.phone IN (?)"
|
56
|
+
[where, phones]
|
57
|
+
end
|
58
|
+
|
59
|
+
def empty_scope
|
60
|
+
Spree.user_class.none
|
61
|
+
end
|
62
|
+
|
63
|
+
def scope
|
64
|
+
Spree.user_class.eager_load(:bill_address, :orders)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Customer
|
4
|
+
module Registered
|
5
|
+
class DetailedFinder
|
6
|
+
def initialize(customer:)
|
7
|
+
@customer = customer
|
8
|
+
end
|
9
|
+
|
10
|
+
def execute
|
11
|
+
return empty_result if customer.nil?
|
12
|
+
|
13
|
+
OpenStruct.new(customer: customer, transactions: transactions, guest: false)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
attr_reader :customer
|
19
|
+
|
20
|
+
def transactions
|
21
|
+
customer_orders = "(#{order_table}.user_id = ?)"
|
22
|
+
guest_orders = "(#{order_table}.user_id IS NULL AND #{order_table}.email = ?)"
|
23
|
+
|
24
|
+
scope = Spree::Order
|
25
|
+
.includes(SpreeGladly::Config.order_includes)
|
26
|
+
.where(state: SpreeGladly::Config.order_states)
|
27
|
+
.order(SpreeGladly::Config.order_sorting)
|
28
|
+
.where("#{customer_orders} OR #{guest_orders}", customer.id, customer.email)
|
29
|
+
|
30
|
+
scope = scope.limit(SpreeGladly::Config.order_limit) if SpreeGladly::Config.order_limit
|
31
|
+
scope.to_a
|
32
|
+
end
|
33
|
+
|
34
|
+
def empty_result
|
35
|
+
OpenStruct.new(customer: [], transactions: [], guest: false)
|
36
|
+
end
|
37
|
+
|
38
|
+
def order_table
|
39
|
+
Spree::Order.table_name
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module SpreeGladly
|
2
|
+
class Configuration < ::Spree::Preferences::Configuration
|
3
|
+
preference :signing_key, :string, default: ''
|
4
|
+
preference :signing_threshold, :integer, default: 0
|
5
|
+
|
6
|
+
attr_accessor :basic_lookup_presenter,
|
7
|
+
:detailed_lookup_presenter,
|
8
|
+
:order_limit,
|
9
|
+
:order_includes,
|
10
|
+
:order_sorting,
|
11
|
+
:order_states
|
12
|
+
|
13
|
+
@basic_lookup_presenter = nil
|
14
|
+
|
15
|
+
@detailed_lookup_presenter = nil
|
16
|
+
|
17
|
+
@order_limit = nil
|
18
|
+
|
19
|
+
@order_includes = nil
|
20
|
+
|
21
|
+
@order_sorting = nil
|
22
|
+
|
23
|
+
@order_states = nil
|
24
|
+
end
|
25
|
+
end
|