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.
		
		
		
		
		
			
		
			
				
					
					
						
							252 lines
						
					
					
						
							8.8 KiB
						
					
					
				
			
		
		
	
	
							252 lines
						
					
					
						
							8.8 KiB
						
					
					
				| # frozen_string_literal: true
 | |
| 
 | |
| require 'tempfile'
 | |
| require 'base64'
 | |
| 
 | |
| module Api
 | |
|   class TemplatesController < ApiBaseController
 | |
|     skip_authorization_check
 | |
|     load_and_authorize_resource :template, except: [:pdf]
 | |
| 
 | |
|     def index
 | |
|       templates = filter_templates(@templates, params)
 | |
| 
 | |
|       templates = paginate(templates.preload(:author, :folder))
 | |
| 
 | |
|       schema_documents =
 | |
|         ActiveStorage::Attachment.where(record_id: templates.map(&:id),
 | |
|                                         record_type: 'Template',
 | |
|                                         name: :documents,
 | |
|                                         uuid: templates.flat_map { |t| t.schema.pluck('attachment_uuid') })
 | |
|                                  .preload(:blob)
 | |
| 
 | |
|       preview_image_attachments =
 | |
|         ActiveStorage::Attachment.joins(:blob)
 | |
|                                  .where(blob: { filename: ['0.png', '0.jpg'] })
 | |
|                                  .where(record_id: schema_documents.map(&:id),
 | |
|                                         record_type: 'ActiveStorage::Attachment',
 | |
|                                         name: :preview_images)
 | |
|                                  .preload(:blob)
 | |
| 
 | |
|       render json: {
 | |
|         data: templates.map do |t|
 | |
|           Templates::SerializeForApi.call(
 | |
|             t,
 | |
|             schema_documents.select { |e| e.record_id == t.id },
 | |
|             preview_image_attachments
 | |
|           )
 | |
|         end,
 | |
|         pagination: {
 | |
|           count: templates.size,
 | |
|           next: templates.last&.id,
 | |
|           prev: templates.first&.id
 | |
|         }
 | |
|       }
 | |
|     end
 | |
| 
 | |
|     def show
 | |
|       render json: Templates::SerializeForApi.call(@template)
 | |
|     end
 | |
| 
 | |
|     def update
 | |
|       if (folder_name = params[:folder_name] || params.dig(:template, :folder_name))
 | |
|         @template.folder = TemplateFolders.find_or_create_by_name(current_user, folder_name)
 | |
|       end
 | |
| 
 | |
|       Array.wrap(params[:roles].presence || params.dig(:template, :roles).presence).each_with_index do |role, index|
 | |
|         if (item = @template.submitters[index])
 | |
|           item['name'] = role
 | |
|         else
 | |
|           @template.submitters << { 'name' => role, 'uuid' => SecureRandom.uuid }
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       archived = params.key?(:archived) ? params[:archived] : params.dig(:template, :archived)
 | |
| 
 | |
|       if archived.in?([true, false])
 | |
|         @template.archived_at = archived == true ? Time.current : nil
 | |
|       end
 | |
| 
 | |
|       @template.update!(template_params)
 | |
| 
 | |
|       SearchEntries.enqueue_reindex(@template)
 | |
| 
 | |
|       WebhookUrls.for_account_id(@template.account_id, 'template.updated').each do |webhook_url|
 | |
|         SendTemplateUpdatedWebhookRequestJob.perform_async('template_id' => @template.id,
 | |
|                                                            'webhook_url_id' => webhook_url.id)
 | |
|       end
 | |
| 
 | |
|       render json: @template.as_json(only: %i[id updated_at])
 | |
|     end
 | |
| 
 | |
|     def destroy
 | |
|       if params[:permanently].in?(['true', true])
 | |
|         @template.destroy!
 | |
|       else
 | |
|         @template.update!(archived_at: Time.current)
 | |
|       end
 | |
| 
 | |
|       render json: @template.as_json(only: %i[id archived_at])
 | |
|     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
 | |
| 
 | |
|       # Set submitters if provided
 | |
|       if params[:submitters].present?
 | |
|         template.submitters = params[:submitters]
 | |
|       end
 | |
| 
 | |
|       # Set fields if provided
 | |
|       if params[:fields].present?
 | |
|         # We'll set fields after documents are processed to ensure correct attachment_uuid mapping
 | |
|         fields_from_request = params[:fields]
 | |
|       end
 | |
| 
 | |
|       template.save!
 | |
| 
 | |
|       begin
 | |
|         documents = process_documents(template, params[:documents])
 | |
| 
 | |
|         schema = documents.map { |doc| { attachment_uuid: doc.uuid, name: doc.filename.base } }
 | |
| 
 | |
|         if template.fields.blank?
 | |
|           if fields_from_request.present?
 | |
|             # Map the fields to use the correct attachment_uuid from the processed documents
 | |
|             mapped_fields = fields_from_request.map do |field|
 | |
|               field_copy = field.dup
 | |
|               if field_copy['areas'].present?
 | |
|                 field_copy['areas'] = field_copy['areas'].map do |area|
 | |
|                   area_copy = area.dup
 | |
|                   # Use the first document's UUID since we're processing one document at a time
 | |
|                   area_copy['attachment_uuid'] = documents.first.uuid if documents.any?
 | |
|                   area_copy
 | |
|                 end
 | |
|               end
 | |
|               field_copy
 | |
|             end
 | |
|             template.fields = mapped_fields
 | |
|           else
 | |
|             template.fields = Templates::ProcessDocument.normalize_attachment_fields(template, documents)
 | |
|             schema.each { |item| item['pending_fields'] = true } if template.fields.present?
 | |
|           end
 | |
|         end
 | |
| 
 | |
|         template.update!(schema: schema)
 | |
| 
 | |
|         enqueue_template_created_webhooks(template)
 | |
| 
 | |
|         SearchEntries.enqueue_reindex(template)
 | |
| 
 | |
|         # Get the documents for serialization
 | |
|         template_documents = template.documents.where(uuid: documents.map(&:uuid))
 | |
| 
 | |
|         result = Templates::SerializeForApi.call(template, template_documents)
 | |
| 
 | |
|         render json: result
 | |
|       rescue StandardError => e
 | |
|         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
 | |
| 
 | |
|     def process_documents(template, documents_params)
 | |
|       return [] if documents_params.blank?
 | |
| 
 | |
|       documents_params.map.with_index do |doc_param, index|
 | |
|         expected_length = (doc_param[:file].length / 4.0 * 3).ceil
 | |
|         # Validate base64 string
 | |
|         unless doc_param[:file].match?(/\A[A-Za-z0-9+\/]*={0,2}\z/)
 | |
|           raise ArgumentError, "Invalid base64 string format"
 | |
|         end
 | |
| 
 | |
|         # Decode base64 file data
 | |
|         file_data = Base64.decode64(doc_param[:file])
 | |
| 
 | |
|         # Check if the decoded data looks like a PDF
 | |
|         if file_data.size >= 4
 | |
|           pdf_header = file_data[0..3]
 | |
|         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' }
 | |
| 
 | |
|         result = Templates::CreateAttachments.handle_pdf_or_image(template, file, file_data, {}, extract_fields: true)
 | |
|         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)
 | |
|       templates = Templates.search(current_user, templates, params[:q])
 | |
|       templates = params[:archived].in?(['true', true]) ? templates.archived : templates.active
 | |
|       templates = templates.where(external_id: params[:application_key]) if params[:application_key].present?
 | |
|       templates = templates.where(external_id: params[:external_id]) if params[:external_id].present?
 | |
|       templates = templates.where(slug: params[:slug]) if params[:slug].present?
 | |
| 
 | |
|       if params[:folder].present?
 | |
|         folder = TemplateFolder.accessible_by(current_ability).find_by(name: params[:folder])
 | |
| 
 | |
|         templates = folder ? templates.where(folder:) : templates.none
 | |
|       end
 | |
| 
 | |
|       templates
 | |
|     end
 | |
| 
 | |
|     def template_params
 | |
|       permitted_params = [
 | |
|         :name,
 | |
|         :external_id,
 | |
|         :shared_link,
 | |
|         {
 | |
|           external_data_fields: {},
 | |
|           submitters: [%i[name uuid is_requester invite_by_uuid optional_invite_by_uuid linked_to_uuid email]],
 | |
|           fields: [[:uuid, :question_id, :submitter_uuid, :name, :type,
 | |
|                     :required, :readonly, :default_value,
 | |
|                     :title, :description,
 | |
|                     { preferences: {},
 | |
|                       conditions: [%i[field_uuid value action operation]],
 | |
|                       options: [%i[value uuid answer_id]],
 | |
|                       validation: %i[message pattern],
 | |
|                       areas: [%i[x y w h cell_w attachment_uuid option_uuid answer_id page]] }]]
 | |
|         }
 | |
|       ]
 | |
| 
 | |
|       if params.key?(:template)
 | |
|         params.require(:template).permit(permitted_params)
 | |
|       else
 | |
|         params.permit(permitted_params)
 | |
|       end
 | |
|     end
 | |
|   end
 | |
| end
 |