diff --git a/.rubocop.yml b/.rubocop.yml
index 721e6c95..4406e2f2 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -39,6 +39,12 @@ Metrics/PerceivedComplexity:
Metrics/AbcSize:
Max: 35
+Metrics/ModuleLength:
+ Max: 500
+
+Metrics/ClassLength:
+ Max: 500
+
RSpec/NestedGroups:
Max: 6
diff --git a/app/controllers/api/attachments_controller.rb b/app/controllers/api/attachments_controller.rb
index 12c6a606..7d13d704 100644
--- a/app/controllers/api/attachments_controller.rb
+++ b/app/controllers/api/attachments_controller.rb
@@ -5,14 +5,14 @@ module Api
skip_before_action :authenticate_user!
def create
- submission = Submission.find_by!(slug: params[:submission_slug]) unless current_account
+ submitter = Submitter.find_by!(slug: params[:submitter_slug]) unless current_account
blob = ActiveStorage::Blob.find_signed(params[:blob_signed_id])
attachment = ActiveStorage::Attachment.create!(
blob:,
name: params[:name],
- record: submission || current_account
+ record: submitter || current_account
)
render json: attachment.as_json(only: %i[uuid], methods: %i[url filename content_type])
diff --git a/app/controllers/send_submission_email_controller.rb b/app/controllers/send_submission_email_controller.rb
index 66bc97fa..f0a40954 100644
--- a/app/controllers/send_submission_email_controller.rb
+++ b/app/controllers/send_submission_email_controller.rb
@@ -9,14 +9,15 @@ class SendSubmissionEmailController < ApplicationController
def success; end
def create
- @submission =
+ @submitter =
if params[:template_slug]
- Submission.joins(:template).find_by!(email: params[:email], template: { slug: params[:template_slug] })
+ Submitter.joins(submission: :template).find_by!(email: params[:email],
+ template: { slug: params[:template_slug] })
else
- Submission.find_by!(slug: params[:submission_slug])
+ Submitter.find_by!(slug: params[:submitter_slug])
end
- SubmissionMailer.copy_to_submitter(@submission).deliver_later!
+ SubmitterMailer.copy_to_submitter(@submitter).deliver_later!
respond_to do |f|
f.html { redirect_to success_send_submission_email_index_path }
diff --git a/app/controllers/start_form_controller.rb b/app/controllers/start_form_controller.rb
index bf4021dc..e7f2d160 100644
--- a/app/controllers/start_form_controller.rb
+++ b/app/controllers/start_form_controller.rb
@@ -8,25 +8,27 @@ class StartFormController < ApplicationController
before_action :load_template
def show
- @submission = @template.submissions.new
+ @submitter = @template.submissions.new.submitters.new(uuid: @template.submitters.first['uuid'])
end
def update
- @submission = @template.submissions.find_or_initialize_by(
- deleted_at: nil, **submission_params
- )
+ @submitter = Submitter.where(submission: @template.submissions.where(submission: { deleted_at: nil }))
+ .find_or_initialize_by(**submitter_params)
- if @submission.completed_at?
+ if @submitter.completed_at?
redirect_to start_form_completed_path(@template.slug, email: submission_params[:email])
else
- @submission.assign_attributes(
+ @submitter.assign_attributes(
+ uuid: @template.submitters.first['uuid'],
opened_at: Time.current,
ip: request.remote_ip,
ua: request.user_agent
)
- if @submission.save
- redirect_to submit_form_path(@submission.slug)
+ @submitter.build_submission(template: @template)
+
+ if @submitter.save
+ redirect_to submit_form_path(@submitter.slug)
else
render :show
end
@@ -34,13 +36,13 @@ class StartFormController < ApplicationController
end
def completed
- @submission = @template.submissions.find_by(email: params[:email])
+ @submitter = Submitter.where(submission: @template.submitters).find_by(email: params[:email])
end
private
- def submission_params
- params.require(:submission).permit(:email)
+ def submitter_params
+ params.require(:submitter).permit(:email)
end
def load_template
diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb
index 2851e526..3a2d61e6 100644
--- a/app/controllers/submissions_controller.rb
+++ b/app/controllers/submissions_controller.rb
@@ -17,18 +17,18 @@ class SubmissionsController < ApplicationController
def new; end
def create
- emails = params[:emails].to_s.scan(User::EMAIL_REGEXP)
-
submissions =
- emails.map do |email|
- submission = @template.submissions.create!(email:, sent_at: params[:send_email] == '1' ? Time.current : nil)
-
- if params[:send_email] == '1'
- SubmissionMailer.invitation_email(submission, message: params[:message]).deliver_later!
- end
+ if params[:emails].present?
+ create_submissions_from_emails
+ else
+ create_submissions_from_submitters
+ end
- submission
+ if params[:send_email] == '1'
+ submissions.flat_map(&:submitters).each do |submitter|
+ SubmitterMailer.invitation_email(submitter, message: params[:message]).deliver_later!
end
+ end
redirect_to template_submissions_path(@template), notice: "#{submissions.size} recepients added"
end
@@ -44,6 +44,34 @@ class SubmissionsController < ApplicationController
private
+ def create_submissions_from_emails
+ emails = params[:emails].to_s.scan(User::EMAIL_REGEXP)
+
+ emails.map do |email|
+ submission = @template.submissions.new
+ submission.submitters.new(email:, uuid: @template.submitters.first['uuid'],
+ sent_at: params[:send_email] == '1' ? Time.current : nil)
+
+ submission.tap(&:save!)
+ end
+ end
+
+ def create_submissions_from_submitters
+ submissions_params.map do |attrs|
+ submission = @template.submissions.new
+
+ attrs[:submitters].each do |submitter_attrs|
+ submission.submitters.new(**submitter_attrs, sent_at: params[:send_email] == '1' ? Time.current : nil)
+ end
+
+ submission.tap(&:save!)
+ end
+ end
+
+ def submissions_params
+ params.require(:submission).map { |param| param.permit(submitters: [%i[uuid email]]) }
+ end
+
def load_template
@template = current_account.templates.find(params[:template_id])
end
diff --git a/app/controllers/submissions_debug_controller.rb b/app/controllers/submissions_debug_controller.rb
index 484b0ade..1970e448 100644
--- a/app/controllers/submissions_debug_controller.rb
+++ b/app/controllers/submissions_debug_controller.rb
@@ -6,16 +6,16 @@ class SubmissionsDebugController < ApplicationController
skip_before_action :authenticate_user!
def index
- @submission = Submission.preload({ attachments_attachments: :blob },
- template: { documents_attachments: :blob })
- .find_by(slug: params[:submission_slug])
+ @submitter = Submitter.preload({ attachments_attachments: :blob },
+ submission: { template: { documents_attachments: :blob } })
+ .find_by(slug: params[:submitter_slug])
respond_to do |f|
f.html do
render 'submit_template/show'
end
f.pdf do
- Submissions::GenerateResultAttachments.call(@submission)
+ Submissions::GenerateResultAttachments.call(@submitter.submission)
send_data ActiveStorage::Attachment.where(name: :documents).last.download,
filename: 'debug.pdf',
diff --git a/app/controllers/submissions_download_controller.rb b/app/controllers/submissions_download_controller.rb
index 23c80505..befde1cc 100644
--- a/app/controllers/submissions_download_controller.rb
+++ b/app/controllers/submissions_download_controller.rb
@@ -4,10 +4,10 @@ class SubmissionsDownloadController < ApplicationController
skip_before_action :authenticate_user!
def index
- submission = Submission.find_by(slug: params[:submission_slug])
+ submitter = Submitter.find_by(slug: params[:submitter_slug])
- Submissions::GenerateResultAttachments.call(submission)
+ Submissions::GenerateResultAttachments.call(submitter)
- redirect_to submission.archive.url, allow_other_host: true
+ redirect_to submitter.archive.url, allow_other_host: true
end
end
diff --git a/app/controllers/submit_form_controller.rb b/app/controllers/submit_form_controller.rb
index 74fbfa73..00ac2b65 100644
--- a/app/controllers/submit_form_controller.rb
+++ b/app/controllers/submit_form_controller.rb
@@ -6,24 +6,25 @@ class SubmitFormController < ApplicationController
skip_before_action :authenticate_user!
def show
- @submission = Submission.preload(template: { documents_attachments: { preview_images_attachments: :blob } })
- .find_by!(slug: params[:slug])
+ @submitter =
+ Submitter.preload(submission: { template: { documents_attachments: { preview_images_attachments: :blob } } })
+ .find_by!(slug: params[:slug])
- return redirect_to submit_form_completed_path(@submission.slug) if @submission.completed_at?
+ return redirect_to submit_form_completed_path(@submitter.slug) if @submitter.completed_at?
end
def update
- submission = Submission.find_by!(slug: params[:slug])
- submission.values.merge!(normalized_values)
- submission.completed_at = Time.current if params[:completed] == 'true'
+ submitter = Submitter.find_by!(slug: params[:slug])
+ submitter.values.merge!(normalized_values)
+ submitter.completed_at = Time.current if params[:completed] == 'true'
- submission.save
+ submitter.save
head :ok
end
def completed
- @submission = Submission.find_by!(slug: params[:submit_form_slug])
+ @submitter = Submitter.find_by!(slug: params[:submit_form_slug])
end
private
diff --git a/app/javascript/elements/file_dropzone.js b/app/javascript/elements/file_dropzone.js
index 8da82e77..24285b19 100644
--- a/app/javascript/elements/file_dropzone.js
+++ b/app/javascript/elements/file_dropzone.js
@@ -62,7 +62,7 @@ export default actionable(targetable(class extends HTMLElement {
body: JSON.stringify({
name: this.dataset.name,
blob_signed_id: blob.signed_id,
- submission_slug: this.dataset.submissionSlug
+ submitter_slug: this.dataset.submitterSlug
}),
headers: { 'Content-Type': 'application/json' }
}).then(resp => resp.json()).then((data) => {
diff --git a/app/javascript/form.js b/app/javascript/form.js
index a766a2a3..020e1757 100644
--- a/app/javascript/form.js
+++ b/app/javascript/form.js
@@ -7,7 +7,8 @@ window.customElements.define('submission-form', class extends HTMLElement {
this.appElem = document.createElement('div')
this.app = createApp(Form, {
- submissionSlug: this.dataset.submissionSlug,
+ submitterSlug: this.dataset.submitterSlug,
+ submitterUuid: this.dataset.submitterUuid,
authenticityToken: this.dataset.authenticityToken,
values: reactive(JSON.parse(this.dataset.values)),
attachments: reactive(JSON.parse(this.dataset.attachments)),
diff --git a/app/javascript/submission_form/attachment_step.vue b/app/javascript/submission_form/attachment_step.vue
index c66aafa9..de50cc71 100644
--- a/app/javascript/submission_form/attachment_step.vue
+++ b/app/javascript/submission_form/attachment_step.vue
@@ -33,7 +33,7 @@
Hi -<%= @submission.values %> -<%= link_to 'Download', submission_download_index_url(@submission.slug) %> diff --git a/app/views/submissions/index.html.erb b/app/views/submissions/index.html.erb index 0ea38f5b..116b620e 100644 --- a/app/views/submissions/index.html.erb +++ b/app/views/submissions/index.html.erb @@ -4,16 +4,18 @@ <%= @template.name %> <%= link_to 'Edit', template_path(@template), class: 'btn btn-outline btn-sm' %> -
| + <%= item['name'] %> + | + <% end %> ++ | +
|---|---|
| + + + | + <% end %> ++ × + | +
Form completed - thanks!
-<%= button_to button_title(title: 'Send copy to Email', disabled_with: 'Sending'), send_submission_email_index_path, params: { submission_slug: @submission.slug }, form: { onsubmit: 'event.submitter.disabled = true' } %> -<%= button_to button_title(title: 'Download documents', disabled_with: 'Downloading'), submission_download_index_path(@submission.slug), method: :get, form: { onsubmit: 'event.submitter.disabled = true' } %> +<%= button_to button_title(title: 'Send copy to Email', disabled_with: 'Sending'), send_submission_email_index_path, params: { submitter_slug: @submitter.slug }, form: { onsubmit: 'event.submitter.disabled = true' } %> +<%= button_to button_title(title: 'Download documents', disabled_with: 'Downloading'), submitter_download_index_path(@submitter.slug), method: :get, form: { onsubmit: 'event.submitter.disabled = true' } %> diff --git a/app/views/submit_form/show.html.erb b/app/views/submit_form/show.html.erb index 330b96f2..918cf47e 100644 --- a/app/views/submit_form/show.html.erb +++ b/app/views/submit_form/show.html.erb @@ -1,8 +1,8 @@ -<% attachment_field_uuids = @submission.template.fields.select { |f| f['type'].in?(%w[image signature attachment]) }.pluck('uuid') %> -<% attachments = ActiveStorage::Attachment.where(uuid: @submission.values.values_at(*attachment_field_uuids).flatten).preload(:blob) %> +<% attachment_field_uuids = @submitter.submission.template.fields.select { |f| f['type'].in?(%w[image signature attachment]) }.pluck('uuid') %> +<% attachments = ActiveStorage::Attachment.where(uuid: @submitter.values.values_at(*attachment_field_uuids).flatten).preload(:blob) %>Hi +<%= @submitter.values %> +<%= link_to 'Download', submitter_download_index_url(@submitter.slug) %> diff --git a/app/views/submission_mailer/invitation_email.html.erb b/app/views/submitter_mailer/invitation_email.html.erb similarity index 53% rename from app/views/submission_mailer/invitation_email.html.erb rename to app/views/submitter_mailer/invitation_email.html.erb index 3c71c4c1..5dc22b60 100644 --- a/app/views/submission_mailer/invitation_email.html.erb +++ b/app/views/submitter_mailer/invitation_email.html.erb @@ -1,4 +1,4 @@ -
Hi there
+Hi there,
You have been invited to submit a form:
-<%= link_to 'Submit', submit_form_index_url(slug: @submission.slug) %>
+<%= link_to 'Submit', submit_form_url(slug: @submitter.slug) %>
If you didn't request this, please ignore this email.
diff --git a/config/initializers/active_storage.rb b/config/initializers/active_storage.rb index bf7bf83d..2cf7c277 100644 --- a/config/initializers/active_storage.rb +++ b/config/initializers/active_storage.rb @@ -13,7 +13,7 @@ Rails.configuration.to_prepare do LoadActiveStorageConfigs.call rescue StandardError => e - Rails.logger.debug(e) + Rails.logger.error(e) nil end diff --git a/config/initializers/field_error_proc.rb b/config/initializers/field_error_proc.rb index 2393455d..e47d6c49 100644 --- a/config/initializers/field_error_proc.rb +++ b/config/initializers/field_error_proc.rb @@ -10,10 +10,16 @@ ActionView::Base.field_error_proc = proc do |html_tag, instance| parsed_html_tag = Nokogiri::HTML::DocumentFragment.parse(html_tag) parsed_html_tag.children.add_class 'input-error' + # rubocop:disable Rails/OutputSafety html_tag = parsed_html_tag.to_s.html_safe + # rubocop:enable Rails/OutputSafety result = html_tag - result += ApplicationController.helpers.tag.label(ApplicationController.render(partial: 'shared/field_error', locals: { message: "#{field_name} #{errors}" }), class: 'label') if errors.present? + + if errors.present? + result += + ApplicationController.render(partial: 'shared/field_error', locals: { message: "#{field_name} #{errors}" }) + end result end diff --git a/config/routes.rb b/config/routes.rb index df2ef200..daa8fa4e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -34,11 +34,11 @@ Rails.application.routes.draw do resources :submissions, only: %i[index new create] end - resources :start_form, only: %i[show update], path: 'f', param: 'slug' do + resources :start_form, only: %i[show update], path: 't', param: 'slug' do get :completed end - resources :submit_form, only: %i[show update], path: 'l', param: 'slug' do + resources :submit_form, only: %i[show update], path: 's', param: 'slug' do get :completed end @@ -46,7 +46,7 @@ Rails.application.routes.draw do get :success, on: :collection end - resources :submissions, only: %i[], param: 'slug' do + resources :submitters, only: %i[], param: 'slug' do resources :download, only: %i[index], controller: 'submissions_download' resources :debug, only: %i[index], controller: 'submissions_debug' end diff --git a/db/migrate/20230519144036_create_submissions.rb b/db/migrate/20230519144036_create_submissions.rb index d482bffe..2dcd103d 100644 --- a/db/migrate/20230519144036_create_submissions.rb +++ b/db/migrate/20230519144036_create_submissions.rb @@ -3,16 +3,8 @@ class CreateSubmissions < ActiveRecord::Migration[7.0] def change create_table :submissions do |t| - t.string :email, null: false, index: true - t.string :slug, null: false, index: { unique: true } t.references :template, null: false, foreign_key: true, index: true - t.string :values, null: false - t.string :ua - t.string :ip - t.datetime :sent_at - t.datetime :opened_at - t.datetime :completed_at t.datetime :deleted_at t.timestamps diff --git a/db/migrate/20230612182744_create_submitters.rb b/db/migrate/20230612182744_create_submitters.rb new file mode 100644 index 00000000..0bf4a45a --- /dev/null +++ b/db/migrate/20230612182744_create_submitters.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class CreateSubmitters < ActiveRecord::Migration[7.0] + def change + create_table :submitters do |t| + t.references :submission, null: false, foreign_key: true, index: true + + t.string :uuid, null: false + t.string :email, null: false, index: true + t.string :slug, null: false, index: { unique: true } + t.string :values, null: false + t.string :ua + t.string :ip + + t.datetime :sent_at + t.datetime :opened_at + t.datetime :completed_at + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index da90df29..14ac7708 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_05_19_144036) do +ActiveRecord::Schema[7.0].define(version: 2023_06_12_182744) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -61,21 +61,29 @@ ActiveRecord::Schema[7.0].define(version: 2023_05_19_144036) do end create_table "submissions", force: :cascade do |t| + t.bigint "template_id", null: false + t.datetime "deleted_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["template_id"], name: "index_submissions_on_template_id" + end + + create_table "submitters", force: :cascade do |t| + t.bigint "submission_id", null: false + t.string "uuid", null: false t.string "email", null: false t.string "slug", null: false - t.bigint "template_id", null: false t.string "values", null: false t.string "ua" t.string "ip" t.datetime "sent_at" t.datetime "opened_at" t.datetime "completed_at" - t.datetime "deleted_at" t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.index ["email"], name: "index_submissions_on_email" - t.index ["slug"], name: "index_submissions_on_slug", unique: true - t.index ["template_id"], name: "index_submissions_on_template_id" + t.index ["email"], name: "index_submitters_on_email" + t.index ["slug"], name: "index_submitters_on_slug", unique: true + t.index ["submission_id"], name: "index_submitters_on_submission_id" end create_table "templates", force: :cascade do |t| @@ -125,6 +133,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_05_19_144036) do add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id" add_foreign_key "encrypted_configs", "accounts" add_foreign_key "submissions", "templates" + add_foreign_key "submitters", "submissions" add_foreign_key "templates", "accounts" add_foreign_key "templates", "users", column: "author_id" add_foreign_key "users", "accounts" diff --git a/lib/submissions/generate_result_attachments.rb b/lib/submissions/generate_result_attachments.rb index 014c42ac..643648ff 100644 --- a/lib/submissions/generate_result_attachments.rb +++ b/lib/submissions/generate_result_attachments.rb @@ -8,19 +8,18 @@ module Submissions module_function # rubocop:disable Metrics - def call(submission) - cert = submission.template.account.encrypted_configs - .find_by(key: EncryptedConfig::ESIGN_CERTS_KEY).value + def call(submitter) + template = submitter.submission.template + + cert = submitter.submission.template.account.encrypted_configs + .find_by(key: EncryptedConfig::ESIGN_CERTS_KEY).value zip_file = Tempfile.new zip_stream = Zip::ZipOutputStream.open(zip_file) - pdfs_index = - submission.template.documents.to_h do |attachment| - [attachment.uuid, HexaPDF::Document.new(io: StringIO.new(attachment.download))] - end + pdfs_index = build_pdfs_index(submitter) - submission.template.fields.each do |field| + template.fields.each do |field| field.fetch('areas', []).each do |area| pdf = pdfs_index[area['attachment_uuid']] @@ -29,7 +28,7 @@ module Submissions width = page.box.width height = page.box.height - value = submission.values[field['uuid']] + value = submitter.values[field['uuid']] canvas = page.canvas(type: :overlay) @@ -88,7 +87,8 @@ module Submissions end when 'date' canvas.font(FONT_NAME, size: FONT_SIZE) - canvas.text(I18n.l(Date.parse(value)), at: [area['x'] * width, height - ((area['y'] * height) + FONT_SIZE)]) + canvas.text(I18n.l(Date.parse(value)), + at: [area['x'] * width, height - ((area['y'] * height) + FONT_SIZE)]) else canvas.font(FONT_NAME, size: FONT_SIZE) canvas.text(value.to_s, at: [area['x'] * width, height - ((area['y'] * height) + FONT_SIZE)]) @@ -96,14 +96,14 @@ module Submissions end end - submission.template.schema.map do |item| - document = submission.template.documents.find { |a| a.uuid == item['attachment_uuid'] } + template.schema.map do |item| + template.documents.find { |a| a.uuid == item['attachment_uuid'] } io = StringIO.new pdf = pdfs_index[item['attachment_uuid']] - pdf.sign(io, reason: "Signed by #{submission.email}", + pdf.sign(io, reason: "Signed by #{submitter.email}", # doc_mdp_permissions: :no_changes, certificate: OpenSSL::X509::Certificate.new(cert['cert']), key: OpenSSL::PKey::RSA.new(cert['key']), @@ -113,13 +113,33 @@ module Submissions zip_stream.put_next_entry("#{item['name']}.pdf") zip_stream.write(io.string) - submission.documents.attach(io: StringIO.new(io.string), filename: document.filename) + ActiveStorage::Attachment.create!( + uuid: item['attachment_uuid'], + blob: ActiveStorage::Blob.create_and_upload!( + io: StringIO.new(io.string), filename: "#{item['name']}.pdf" + ), + name: 'documents', + record: submitter + ) end zip_stream.close - submission.archive.attach(io: zip_file, filename: "#{submission.template.name}.zip") + submitter.archive.attach(io: zip_file, filename: "#{template.name}.zip") end # rubocop:enable Metrics + + def build_pdfs_index(submitter) + latest_submitter = submitter.submission.submitters + .select { |e| e.id != submitter.id && e.completed_at? } + .max_by(&:completed_at) + + documents = latest_submitter&.documents.to_a.presence + documents ||= submitter.submission.template.documents + + documents.to_h do |attachment| + [attachment.uuid, HexaPDF::Document.new(io: StringIO.new(attachment.download))] + end + end end end