Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions app/controllers/spree/api/v2/platform/clone_stores_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ module Platform
class CloneStoresController < Spree::Olitt::CloneStore::CloneStoreController
skip_forgery_protection

attr_reader :current_api_key

rescue_from Doorkeeper::Errors::DoorkeeperError, with: :render_unauthorized
rescue_from ActiveRecord::RecordNotUnique, with: :render_bad_request_exception
rescue_from ActiveRecord::RecordInvalid, with: :render_record_invalid
Expand All @@ -25,11 +27,17 @@ def show
private

def authorize_clone_store_request!
if api_key_header_present?
authenticate_secret_key!
return
end

scopes = action_name == 'show' ? %i[read admin] : %i[write admin]
doorkeeper_authorize!(*scopes)
end

def authorize_superadmin_user_token!
return if current_api_key.present?
return if spree_current_user.nil?
return if superuser_with_global_admin_role?(spree_current_user)

Expand All @@ -45,11 +53,39 @@ def render_unauthorized(_exception)
end

def validate_token_client
return if api_key_header_present?
return if doorkeeper_token.nil?

raise Doorkeeper::Errors::DoorkeeperError if doorkeeper_token.application.nil?
end

def authenticate_secret_key!
@current_api_key = Spree::ApiKey.find_by(token_digest: Spree::ApiKey.compute_token_digest(extract_api_key))
@current_api_key = nil if @current_api_key && (current_store.blank? || @current_api_key.store_id != current_store.id)

unless @current_api_key
render_api_error('Valid secret API key required', :unauthorized)
return false
end

touch_api_key_if_needed(@current_api_key)
true
end

def touch_api_key_if_needed(api_key)
return if api_key.last_used_at.present? && api_key.last_used_at > 1.hour.ago

Spree::ApiKeys::MarkAsUsed.perform_later(api_key.id, Time.current)
end

def extract_api_key
request.headers['X-Spree-Api-Key'].presence
end

def api_key_header_present?
extract_api_key.present?
end

def render_api_error(message, status)
render json: { error: message }, status: status
end
Expand Down
58 changes: 46 additions & 12 deletions app/controllers/spree/olitt/clone_store/clone_store_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@ def render_error(duplicator:)
def handle_create_vendor(email, password, password_confirmation)
user_email = email.to_s.strip.downcase
@vendor = find_or_create_vendor(user_email)
user = find_or_create_user(user_email, password, password_confirmation)
assign_vendor_role(user, @vendor)
legacy_user = existing_legacy_user(user_email)
admin_user = find_or_create_admin_user(user_email, password, password_confirmation, legacy_user: legacy_user)
assign_vendor_role(admin_user, @vendor)
link_admin_user_to_vendor!(vendor: @vendor, admin_user: admin_user, legacy_user: legacy_user)
activate_vendor(@vendor)
end

Expand Down Expand Up @@ -165,27 +167,59 @@ def find_or_create_vendor(email)
)
end

def find_or_create_user(email, password, password_confirmation)
user = Spree.user_class.find_or_initialize_by(email: email)
return user if user.persisted?
def find_or_create_admin_user(email, password, password_confirmation, legacy_user: nil)
admin_user = Spree.admin_user_class.find_or_initialize_by(email: email)
return admin_user if admin_user.persisted?

user.password = password
user.password_confirmation = password_confirmation.presence || password
user.save!
user
admin_user.login ||= email if admin_user.respond_to?(:login=)
admin_user.password = password
admin_user.password_confirmation = password_confirmation.presence || password if admin_user.respond_to?(:password_confirmation=)

if legacy_user.present?
admin_user.first_name ||= legacy_user.first_name if admin_user.respond_to?(:first_name=)
admin_user.last_name ||= legacy_user.last_name if admin_user.respond_to?(:last_name=)
admin_user.selected_locale ||= legacy_user.selected_locale if admin_user.respond_to?(:selected_locale=)
end

admin_user.save!
admin_user
end

def assign_vendor_role(user, vendor)
def assign_vendor_role(admin_user, vendor)
vendor_role_name = defined?(Spree::Vendor::DEFAULT_VENDOR_ROLE) ? Spree::Vendor::DEFAULT_VENDOR_ROLE : 'vendor'
vendor_role = Spree::Role.find_or_create_by!(name: vendor_role_name)
vendor_role = vendor.respond_to?(:default_user_role) ? (vendor.default_user_role || Spree::Role.find_or_create_by!(name: vendor_role_name)) : Spree::Role.find_or_create_by!(name: vendor_role_name)

Spree::RoleUser.find_or_create_by!(
user: user,
user: admin_user,
role: vendor_role,
resource: vendor
)
end

def existing_legacy_user(email)
return nil if Spree.user_class == Spree.admin_user_class

Spree.user_class.find_by(email: email)
end

def link_admin_user_to_vendor!(vendor:, admin_user:, legacy_user: nil)
return unless defined?(Spree::VendorUser)
return unless ActiveRecord::Base.connection.data_source_exists?('spree_vendor_users')

vendor_user = if legacy_user.present?
Spree::VendorUser.find_by(vendor_id: vendor.id, user_id: legacy_user.id)
end

if vendor_user.nil? && Spree::VendorUser.column_names.include?('admin_user_id')
vendor_user = Spree::VendorUser.find_by(vendor_id: vendor.id, admin_user_id: admin_user.id)
end

vendor_user ||= Spree::VendorUser.new(vendor_id: vendor.id)
vendor_user.user_id = legacy_user.id if legacy_user.present? && vendor_user.respond_to?(:user_id=) && vendor_user.user_id.blank?
vendor_user.admin_user_id = admin_user.id if vendor_user.respond_to?(:admin_user_id=)
vendor_user.save! if vendor_user.new_record? || vendor_user.changed?
end

def activate_vendor(vendor)
return if %w[active approved].include?(vendor.state)

Expand Down
2 changes: 1 addition & 1 deletion app/helpers/spree/olitt/clone_store/clone_store_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def vendor_params
end

def source_id_param
clone_store_payload.require(:source_store_id)
clone_store_payload[:source_store_id].presence || Spree::Store.default&.id
end

def resource_serializer
Expand Down
2 changes: 1 addition & 1 deletion app/helpers/spree/olitt/clone_store/product_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def assign_variant_prices(new_variant:, old_variant:)

if prices.any? && new_variant.respond_to?(:prices=)
new_variant.prices = prices
elsif old_variant.respond_to?(:price) && new_variant.respond_to?(:price=)
elsif old_variant.respond_to?(:price) && new_variant.respond_to?(:price=) && old_variant.price.present?
new_variant.price = old_variant.price
new_variant.currency = old_variant.currency if new_variant.respond_to?(:currency=) && old_variant.respond_to?(:currency)
end
Expand Down
13 changes: 13 additions & 0 deletions app/models/spree/admin_user_decorator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module Spree
module AdminUserDecorator
def self.prepended(base)
base.has_many :clone_requests,
class_name: 'Spree::Olitt::CloneStore::CloneRequest',
foreign_key: :user_id,
inverse_of: :admin_user,
dependent: :nullify
end
end
end

Spree.admin_user_class.prepend(Spree::AdminUserDecorator) unless Spree.admin_user_class < Spree::AdminUserDecorator
18 changes: 13 additions & 5 deletions app/models/spree/olitt/clone_store/clone_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,19 @@ class CloneRequest < ::ApplicationRecord
belongs_to :source_store, class_name: 'Spree::Store'
belongs_to :store, class_name: 'Spree::Store', optional: true
belongs_to :vendor, class_name: 'Spree::Vendor', optional: true
belongs_to :user, class_name: Spree.user_class.to_s, optional: true
belongs_to :admin_user, class_name: Spree.admin_user_class.to_s, foreign_key: :user_id, optional: true
belongs_to :role_user, class_name: 'Spree::RoleUser', optional: true

validates :store_name, :store_url, :store_code, :store_mail_from_address, :vendor_email, :vendor_password, presence: true

def user
admin_user
end

def user=(value)
self.admin_user = value
end

def mark_enqueued!(job)
update!(
job_id: job.job_id,
Expand All @@ -48,17 +56,17 @@ def mark_failed!(message)
def cleanup_failed_clone!
cloned_store = store
assigned_role_user = created_role_user? ? role_user : nil
assigned_user = created_user? ? user : nil
assigned_admin_user = created_user? ? admin_user : nil
assigned_vendor = created_vendor? ? vendor : nil

update_columns(store_id: nil, role_user_id: nil, user_id: nil, vendor_id: nil)

cloned_store&.destroy!
assigned_role_user&.destroy!

if assigned_user.present?
assigned_user.reload
assigned_user.destroy! if assigned_user.role_users.reload.none?
if assigned_admin_user.present?
assigned_admin_user.reload
assigned_admin_user.destroy! if assigned_admin_user.role_users.reload.none?
end

if assigned_vendor.present?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def initialize(source_store_id:, store_params:, vendor_params:)
def call
validate_vendor_params!

source_store = Spree::Store.find(@source_store_id)
source_store = resolved_source_store

CloneRequest.create!(
source_store: source_store,
Expand Down Expand Up @@ -64,6 +64,13 @@ def extract_record_not_unique_message(error)
raw_message = error.cause&.message.presence || error.message
raw_message.to_s.sub(/\AMysql2::Error:\s*/i, '')
end

def resolved_source_store
return @source_store if @source_store.present?

source_store_id = @source_store_id.presence || Spree::Store.default&.id
Spree::Store.find(source_store_id)
end
end
end
end
Expand Down
22 changes: 20 additions & 2 deletions app/services/spree/olitt/clone_store/clone_request_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ def as_json
private

def resource_payload
return @clone_request.fallback_store_payload if @clone_request.store.blank?
if @clone_request.store.blank?
fallback_payload = @clone_request.fallback_store_payload
return fallback_payload.is_a?(Hash) ? fallback_payload.fetch(:data, fallback_payload['data']) : fallback_payload
end

serialized = @serializer.call(@clone_request.store)
serialized.is_a?(Hash) ? serialized.fetch(:data, serialized['data']) : serialized
Expand All @@ -34,9 +37,24 @@ def metadata
status: @clone_request.status,
source_store_id: @clone_request.source_store_id,
queue_name: @clone_request.queue_name,
queued_at: @clone_request.enqueued_at
queued_at: @clone_request.enqueued_at,
vendor: vendor_metadata
}
end

def vendor_metadata
{
vendor_id: @clone_request.vendor_id,
vendor_slug: @clone_request.vendor&.slug,
admin_user_id: @clone_request.admin_user&.id,
email: @clone_request.vendor_email,
next_path: admin_dashboard_path
}
end

def admin_dashboard_path
'/admin'
end
end
end
end
Expand Down
Loading
Loading