|
|
|
@ -1,8 +1,12 @@
|
|
|
|
# frozen_string_literal: true
|
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
require 'tempfile'
|
|
|
|
|
|
|
|
require 'base64'
|
|
|
|
|
|
|
|
|
|
|
|
module Api
|
|
|
|
module Api
|
|
|
|
class TemplatesController < ApiBaseController
|
|
|
|
class TemplatesController < ApiBaseController
|
|
|
|
load_and_authorize_resource :template
|
|
|
|
skip_authorization_check
|
|
|
|
|
|
|
|
load_and_authorize_resource :template, except: [:pdf]
|
|
|
|
|
|
|
|
|
|
|
|
def index
|
|
|
|
def index
|
|
|
|
templates = filter_templates(@templates, params)
|
|
|
|
templates = filter_templates(@templates, params)
|
|
|
|
@ -85,8 +89,132 @@ module Api
|
|
|
|
render json: @template.as_json(only: %i[id archived_at])
|
|
|
|
render json: @template.as_json(only: %i[id archived_at])
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def pdf
|
|
|
|
|
|
|
|
template = Template.new
|
|
|
|
|
|
|
|
template.account = current_account
|
|
|
|
|
|
|
|
template.author = current_user
|
|
|
|
|
|
|
|
template.folder = TemplateFolders.find_or_create_by_name(current_user, params[:folder_name])
|
|
|
|
|
|
|
|
template.name = params[:name] || 'Untitled Template'
|
|
|
|
|
|
|
|
template.external_id = params[:external_id] if params[:external_id].present?
|
|
|
|
|
|
|
|
template.source = :api
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
template.save!
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
begin
|
|
|
|
|
|
|
|
puts "DEBUG: Starting document processing..."
|
|
|
|
|
|
|
|
documents = process_documents(template, params[:documents])
|
|
|
|
|
|
|
|
puts "DEBUG: Documents processed: #{documents.count}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
schema = documents.map { |doc| { attachment_uuid: doc.uuid, name: doc.filename.base } }
|
|
|
|
|
|
|
|
puts "DEBUG: Schema created: #{schema}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if template.fields.blank?
|
|
|
|
|
|
|
|
puts "DEBUG: Processing fields..."
|
|
|
|
|
|
|
|
template.fields = Templates::ProcessDocument.normalize_attachment_fields(template, documents)
|
|
|
|
|
|
|
|
schema.each { |item| item['pending_fields'] = true } if template.fields.present?
|
|
|
|
|
|
|
|
puts "DEBUG: Fields processed: #{template.fields.count}"
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
puts "DEBUG: Updating template schema..."
|
|
|
|
|
|
|
|
template.update!(schema: schema)
|
|
|
|
|
|
|
|
puts "DEBUG: Template schema updated"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
puts "DEBUG: Enqueueing webhooks..."
|
|
|
|
|
|
|
|
enqueue_template_created_webhooks(template)
|
|
|
|
|
|
|
|
puts "DEBUG: Webhooks enqueued"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
puts "DEBUG: Enqueueing search reindex..."
|
|
|
|
|
|
|
|
SearchEntries.enqueue_reindex(template)
|
|
|
|
|
|
|
|
puts "DEBUG: Search reindex enqueued"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Get the documents for serialization
|
|
|
|
|
|
|
|
puts "DEBUG: Getting template documents for serialization..."
|
|
|
|
|
|
|
|
template_documents = template.documents.where(uuid: documents.map(&:uuid))
|
|
|
|
|
|
|
|
puts "DEBUG: Template documents found: #{template_documents.count}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
puts "DEBUG: Serializing template..."
|
|
|
|
|
|
|
|
result = Templates::SerializeForApi.call(template, template_documents)
|
|
|
|
|
|
|
|
puts "DEBUG: Template serialized successfully"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
render json: result
|
|
|
|
|
|
|
|
rescue StandardError => e
|
|
|
|
|
|
|
|
puts "DEBUG: ERROR OCCURRED: #{e.class} - #{e.message}"
|
|
|
|
|
|
|
|
puts "DEBUG: Backtrace: #{e.backtrace.first(5).join("\n")}"
|
|
|
|
|
|
|
|
template.destroy!
|
|
|
|
|
|
|
|
raise e
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
rescue Templates::CreateAttachments::PdfEncrypted
|
|
|
|
|
|
|
|
render json: { error: 'PDF encrypted', status: 'pdf_encrypted' }, status: :unprocessable_entity
|
|
|
|
|
|
|
|
rescue StandardError => e
|
|
|
|
|
|
|
|
Rollbar.error(e) if defined?(Rollbar)
|
|
|
|
|
|
|
|
render json: { error: 'Unable to create template' }, status: :unprocessable_entity
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
private
|
|
|
|
private
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def process_documents(template, documents_params)
|
|
|
|
|
|
|
|
puts "DEBUG: process_documents called with #{documents_params&.count || 0} documents"
|
|
|
|
|
|
|
|
return [] if documents_params.blank?
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
documents_params.map.with_index do |doc_param, index|
|
|
|
|
|
|
|
|
puts "DEBUG: Processing document #{index + 1}: #{doc_param[:name]}"
|
|
|
|
|
|
|
|
puts "DEBUG: Base64 string length: #{doc_param[:file].length}"
|
|
|
|
|
|
|
|
puts "DEBUG: Base64 string first 100 chars: #{doc_param[:file][0..99]}"
|
|
|
|
|
|
|
|
puts "DEBUG: Base64 string last 100 chars: #{doc_param[:file][-100..-1]}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Check if the base64 string looks truncated
|
|
|
|
|
|
|
|
expected_length = (doc_param[:file].length / 4.0 * 3).ceil
|
|
|
|
|
|
|
|
puts "DEBUG: Expected decoded size: #{expected_length} bytes"
|
|
|
|
|
|
|
|
puts "DEBUG: Base64 string ends with padding: #{doc_param[:file].end_with?('==') || doc_param[:file].end_with?('=')}"
|
|
|
|
|
|
|
|
puts "DEBUG: Base64 string length is multiple of 4: #{doc_param[:file].length % 4 == 0}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Validate base64 string
|
|
|
|
|
|
|
|
unless doc_param[:file].match?(/\A[A-Za-z0-9+\/]*={0,2}\z/)
|
|
|
|
|
|
|
|
puts "DEBUG: ERROR: Invalid base64 string format"
|
|
|
|
|
|
|
|
raise ArgumentError, "Invalid base64 string format"
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Decode base64 file data
|
|
|
|
|
|
|
|
file_data = Base64.decode64(doc_param[:file])
|
|
|
|
|
|
|
|
puts "DEBUG: File data decoded, size: #{file_data.size} bytes"
|
|
|
|
|
|
|
|
puts "DEBUG: First 50 bytes as hex: #{file_data[0..49].unpack('H*').first}"
|
|
|
|
|
|
|
|
puts "DEBUG: First 50 bytes as string: #{file_data[0..49].inspect}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Check if the decoded data looks like a PDF
|
|
|
|
|
|
|
|
if file_data.size >= 4
|
|
|
|
|
|
|
|
pdf_header = file_data[0..3]
|
|
|
|
|
|
|
|
puts "DEBUG: PDF header: #{pdf_header.inspect}"
|
|
|
|
|
|
|
|
puts "DEBUG: Is PDF header valid: #{pdf_header == '%PDF'}"
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Create a temporary file-like object
|
|
|
|
|
|
|
|
file = Tempfile.new(['document', '.pdf'])
|
|
|
|
|
|
|
|
file.binmode
|
|
|
|
|
|
|
|
file.write(file_data)
|
|
|
|
|
|
|
|
file.rewind
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Add original filename
|
|
|
|
|
|
|
|
file.define_singleton_method(:original_filename) { doc_param[:name] }
|
|
|
|
|
|
|
|
file.define_singleton_method(:content_type) { 'application/pdf' }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
puts "DEBUG: Calling Templates::CreateAttachments.handle_pdf_or_image..."
|
|
|
|
|
|
|
|
# Process the document using the existing service
|
|
|
|
|
|
|
|
result = Templates::CreateAttachments.handle_pdf_or_image(template, file, file_data, {}, extract_fields: true)
|
|
|
|
|
|
|
|
puts "DEBUG: Document processed successfully: #{result.class}"
|
|
|
|
|
|
|
|
result
|
|
|
|
|
|
|
|
ensure
|
|
|
|
|
|
|
|
file&.close
|
|
|
|
|
|
|
|
file&.unlink
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def enqueue_template_created_webhooks(template)
|
|
|
|
|
|
|
|
WebhookUrls.for_account_id(template.account_id, 'template.created').each do |webhook_url|
|
|
|
|
|
|
|
|
SendTemplateCreatedWebhookRequestJob.perform_async('template_id' => template.id,
|
|
|
|
|
|
|
|
'webhook_url_id' => webhook_url.id)
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
def filter_templates(templates, params)
|
|
|
|
def filter_templates(templates, params)
|
|
|
|
templates = Templates.search(current_user, templates, params[:q])
|
|
|
|
templates = Templates.search(current_user, templates, params[:q])
|
|
|
|
templates = params[:archived].in?(['true', true]) ? templates.archived : templates.active
|
|
|
|
templates = params[:archived].in?(['true', true]) ? templates.archived : templates.active
|
|
|
|
|