mirror of https://github.com/docusealco/docuseal
				
				
				
			
			You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							201 lines
						
					
					
						
							6.1 KiB
						
					
					
				
			
		
		
	
	
							201 lines
						
					
					
						
							6.1 KiB
						
					
					
				| # frozen_string_literal: true
 | |
| 
 | |
| module Submitters
 | |
|   module NormalizeValues
 | |
|     CHECKSUM_CACHE_STORE = ActiveSupport::Cache::MemoryStore.new
 | |
| 
 | |
|     BaseError = Class.new(StandardError)
 | |
| 
 | |
|     UnknownFieldName = Class.new(BaseError)
 | |
|     InvalidDefaultValue = Class.new(BaseError)
 | |
|     UnknownSubmitterName = Class.new(BaseError)
 | |
| 
 | |
|     module_function
 | |
| 
 | |
|     def call(template, values, submitter_name: nil, for_submitter: nil, throw_errors: false)
 | |
|       fields = fetch_fields(template, submitter_name:, for_submitter:)
 | |
| 
 | |
|       fields_uuid_index = fields.index_by { |e| e['uuid'] }
 | |
|       fields_name_index = build_fields_index(fields)
 | |
| 
 | |
|       attachments = []
 | |
| 
 | |
|       normalized_values = values.to_h.filter_map do |key, value|
 | |
|         if fields_uuid_index[key].blank?
 | |
|           original_key = key
 | |
| 
 | |
|           key = fields_name_index[key]&.dig('uuid') || fields_name_index[key.to_s.downcase]&.dig('uuid')
 | |
| 
 | |
|           raise(UnknownFieldName, "Unknown field: #{original_key}") if key.blank? && throw_errors
 | |
|         end
 | |
| 
 | |
|         next if key.blank?
 | |
| 
 | |
|         field = fields_uuid_index[key]
 | |
| 
 | |
|         if field['type'].in?(%w[initials signature image file stamp]) && value.present?
 | |
|           new_value, new_attachments =
 | |
|             normalize_attachment_value(value, field['type'], template.account, attachments, for_submitter)
 | |
| 
 | |
|           attachments.push(*new_attachments)
 | |
| 
 | |
|           value = new_value
 | |
|         end
 | |
| 
 | |
|         [key, normalize_value(field, value)]
 | |
|       end.to_h
 | |
| 
 | |
|       [normalized_values, attachments]
 | |
|     end
 | |
| 
 | |
|     def normalize_value(field, value)
 | |
|       if field['type'] == 'text' && value.present?
 | |
|         value.to_s
 | |
|       elsif field['type'] == 'date' && value.present?
 | |
|         if value.is_a?(Integer)
 | |
|           Time.zone.at(value.to_s.first(10).to_i).to_date
 | |
|         else
 | |
|           Date.parse(value).to_s
 | |
|         end
 | |
|       else
 | |
|         value
 | |
|       end
 | |
|     rescue Date::Error => e
 | |
|       Rollbar.warning(e) if defined?(Rollbar)
 | |
| 
 | |
|       value
 | |
|     end
 | |
| 
 | |
|     def fetch_fields(template, submitter_name: nil, for_submitter: nil)
 | |
|       if submitter_name
 | |
|         submitter =
 | |
|           template.submitters.find { |e| e['name'] == submitter_name } ||
 | |
|           raise(UnknownSubmitterName,
 | |
|                 "Unknown submitter role: #{submitter_name}. Template defines #{template.submitters.pluck('name')}")
 | |
|       end
 | |
| 
 | |
|       fields = for_submitter&.submission&.template_fields || template.fields
 | |
| 
 | |
|       fields.select do |e|
 | |
|         submitter_uuid =
 | |
|           for_submitter&.uuid || submitter&.dig('uuid') ||
 | |
|           raise(UnknownSubmitterName, "Unknown submitter role: template defines #{template.submitters.pluck('name')}")
 | |
| 
 | |
|         e['submitter_uuid'] == submitter_uuid
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     def build_fields_index(fields)
 | |
|       fields.index_by { |e| e['name'] }
 | |
|             .merge(fields.index_by { |e| e['name'].to_s.parameterize.underscore })
 | |
|             .merge(fields.index_by { |e| e['name'].to_s.downcase })
 | |
|     end
 | |
| 
 | |
|     def normalize_attachment_value(value, type, account, attachments, for_submitter = nil)
 | |
|       if value.is_a?(Array)
 | |
|         new_attachments = value.map do |v|
 | |
|           new_attachment = find_or_build_attachment(v, type, account, for_submitter)
 | |
| 
 | |
|           attachments.find { |a| a.blob_id == new_attachment.blob_id } || new_attachment
 | |
|         end
 | |
| 
 | |
|         [new_attachments.map(&:uuid), new_attachments]
 | |
|       else
 | |
|         new_attachment = find_or_build_attachment(value, type, account, for_submitter)
 | |
| 
 | |
|         existing_attachment = attachments.find { |a| a.blob_id == new_attachment.blob_id }
 | |
| 
 | |
|         attachment = existing_attachment || new_attachment
 | |
| 
 | |
|         [attachment.uuid, attachment]
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     def find_or_build_attachment(value, type, account, for_submitter = nil)
 | |
|       blob =
 | |
|         if value.match?(%r{\Ahttps?://})
 | |
|           find_or_create_blob_from_url(account, value)
 | |
|         elsif type.in?(%w[signature initials]) && value.length < 50
 | |
|           find_or_create_blob_from_text(account, value, type)
 | |
|         elsif (data = Base64.decode64(value)) && Marcel::MimeType.for(data).include?('image')
 | |
|           find_or_create_blob_from_base64(account, data, type)
 | |
|         else
 | |
|           raise InvalidDefaultValue, "Invalid value, url, base64 or text < 50 chars is expected: #{value.first(50)}..."
 | |
|         end
 | |
| 
 | |
|       attachment = for_submitter.attachments.find_by(blob_id: blob.id) if for_submitter
 | |
| 
 | |
|       attachment ||= ActiveStorage::Attachment.new(
 | |
|         blob:,
 | |
|         name: 'attachments'
 | |
|       )
 | |
| 
 | |
|       attachment
 | |
|     end
 | |
| 
 | |
|     def find_or_create_blob_from_base64(account, data, type)
 | |
|       checksum = Digest::MD5.base64digest(data)
 | |
| 
 | |
|       blob = find_blob_by_checksum(checksum, account)
 | |
| 
 | |
|       blob || ActiveStorage::Blob.create_and_upload!(
 | |
|         io: StringIO.new(data),
 | |
|         filename: "#{type}.png"
 | |
|       )
 | |
|     end
 | |
| 
 | |
|     def find_or_create_blob_from_text(account, text, type)
 | |
|       data = Submitters::GenerateFontImage.call(text, font: type)
 | |
| 
 | |
|       checksum = Digest::MD5.base64digest(data)
 | |
| 
 | |
|       blob = find_blob_by_checksum(checksum, account)
 | |
| 
 | |
|       blob || ActiveStorage::Blob.create_and_upload!(
 | |
|         io: StringIO.new(data),
 | |
|         filename: "#{type}.png"
 | |
|       )
 | |
|     end
 | |
| 
 | |
|     def find_or_create_blob_from_url(account, url)
 | |
|       cache_key = [account.id, url].join(':')
 | |
|       checksum = CHECKSUM_CACHE_STORE.fetch(cache_key)
 | |
| 
 | |
|       blob = find_blob_by_checksum(checksum, account) if checksum
 | |
| 
 | |
|       return blob if blob
 | |
| 
 | |
|       data = conn.get(Addressable::URI.parse(url).display_uri.to_s).body
 | |
| 
 | |
|       checksum = Digest::MD5.base64digest(data)
 | |
| 
 | |
|       CHECKSUM_CACHE_STORE.write(cache_key, checksum)
 | |
| 
 | |
|       blob = find_blob_by_checksum(checksum, account)
 | |
| 
 | |
|       blob || ActiveStorage::Blob.create_and_upload!(
 | |
|         io: StringIO.new(data),
 | |
|         filename: Addressable::URI.parse(url).path.split('/').last
 | |
|       )
 | |
|     end
 | |
| 
 | |
|     def find_blob_by_checksum(checksum, account)
 | |
|       blob = ActiveStorage::Blob.find_by(checksum:)
 | |
| 
 | |
|       return unless blob
 | |
| 
 | |
|       return blob unless blob.attachments.exists?
 | |
| 
 | |
|       return blob if account.submitters.exists?(id: blob.attachments.where(record_type: 'Submitter').select(:record_id))
 | |
| 
 | |
|       nil
 | |
|     end
 | |
| 
 | |
|     def conn
 | |
|       Faraday.new do |faraday|
 | |
|         faraday.response :follow_redirects
 | |
|       end
 | |
|     end
 | |
|   end
 | |
| end
 |