Skip to content

Instantly share code, notes, and snippets.

@saggineumann
Last active April 4, 2022 10:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save saggineumann/201f419980a4d6cacc04e38b234f8fc4 to your computer and use it in GitHub Desktop.
Save saggineumann/201f419980a4d6cacc04e38b234f8fc4 to your computer and use it in GitHub Desktop.
SFTP Shopify File Synchornization
module FileServices
class FileImporterService < ApplicationService
attr_reader :shop
class FileCreateError < StandardError
end
def initialize(shop)
@shop = shop
end
def call(file_name)
shop.with_shopify_session do
execute_query(file_name)
end
true
end
private
def execute_query(file_name)
full_url = "#{ENV.fetch('SFTPTOGO_PUBLIC_URL')}#{ERB::Util.url_encode(file_name)}"
Rails.logger.info "Creating #{file_name}"
result = ShopifyAPI::GraphQL.client.query(file_create_query, variables: {
"files": {
"originalSource": full_url
}
})
error = result&.errors&.details || result.to_h['data']['fileCreate']['userErrors'].first
raise FileCreateError, error['message'] if error.present?
end
def file_create_query
ShopifyAPI::GraphQL.client.parse <<-'GRAPHQL'
mutation ($files: [FileCreateInput!]!) {
fileCreate(files: $files) {
files {
fileStatus
}
userErrors {
field
message
}
}
}
GRAPHQL
end
end
End
module FileServices
## reads existing filenames from Shopify to avoid duplicates
# then starts reading filenames from FTP and creates new file entries in Shopify
class FileOrganizerService < ApplicationService
attr_reader :shop
def initialize(shop)
@shop = shop
end
def call
# get an array of the existing filenames in the Shopify store to avoid duplicate uploads
existing_filenames = query_existing_files
# get all filenames that are not yet existing in the shopify files
file_names = FileServices::SftpFileLoaderService.new(ENV.fetch('SFTPTOGO_URL'), existing_filenames).call
file_names.each do |file_name|
# Create a new file entry
FileServices::FileImporterService.new(shop).call(file_name)
rescue StandardError => e
# report any errors to everyone's favourite error tracker
Honeybadger.notify("ERROR Uploading #{file_name}: #{e.message}")
end
end
private
def query_existing_files
existing_filenames = []
shop.with_shopify_session do
file_result = ShopifyAPI::GraphQL.client.query(file_search_query)
existing_filenames << query_result_to_array(file_result&.data&.files&.edges)
# if more than one page
while file_result&.data&.files&.page_info&.has_next_page
cursor = file_result&.data&.files&.edges&.last&.cursor
file_result = ShopifyAPI::GraphQL.client.query(file_search_query, variables: { cursor: cursor })
existing_filenames << query_result_to_array(file_result&.data&.files&.edges)
end
end
existing_filenames.flatten
end
def file_search_query
ShopifyAPI::GraphQL.client.parse <<-'GRAPHQL'
query ($cursor: String){
files(first:250, after: $cursor){
pageInfo {
hasNextPage
}
edges{
cursor
node{... on GenericFile {
url
id
}}
node{... on MediaImage {
id
}}
}
}
}
GRAPHQL
end
private
def query_result_to_array(edges)
return unless edges.present?
edges.map do |edge|
if edge&.node&.__typename == 'GenericFile'
File.basename(URI.parse(edge&.node&.url).path) if edge&.node&.url.present?
else
edge&.node&.id
end
end
end
end
end
module FileServices
class SftpFileLoaderService < ApplicationService
attr_reader :sftp_uri, :filename, :existing_filenames
def initialize(sftp_uri, existing_filenames)
@sftp_uri = URI(sftp_uri)
@existing_filenames = existing_filenames
end
def call
load_files
end
private
def sftp
# establish connection to SFTP Server
@sftp ||= Net::SFTP.start(@sftp_uri.host, @sftp_uri.user, password: @sftp_uri.password)
end
# fetches all filenames from the configured directory, that are not in the existing_filenames list
def load_files
file_names = []
dir = ENV.fetch('FTP_FILE_DIR')
sftp.dir.foreach(dir) do |entry|
# skip if file already exists on server
next if existing_filenames.include? entry.name
file_names << entry.name
rescue Net::SFTP::StatusException => e
Rails.logger.error "Error while downloading data: #{e.description}"
end
file_names
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment