From 1f4180741250b84a50f150e86b5fb2f8b42921c8 Mon Sep 17 00:00:00 2001 From: DocuSeal Date: Sun, 1 Oct 2023 01:57:47 +0300 Subject: [PATCH] add more api endpoints --- app/controllers/api/api_base_controller.rb | 16 +++++ app/controllers/api/submissions_controller.rb | 64 ++++++++++++++++++- app/controllers/api/submitters_controller.rb | 13 ++++ app/controllers/api/templates_controller.rb | 37 +++++++++-- app/models/template.rb | 1 + app/views/api_settings/index.html.erb | 47 +++++++------- config/routes.rb | 11 +++- lib/submitters/serialize_for_api.rb | 38 +++++++++++ lib/submitters/serialize_for_webhook.rb | 9 ++- 9 files changed, 203 insertions(+), 33 deletions(-) create mode 100644 app/controllers/api/submitters_controller.rb create mode 100644 lib/submitters/serialize_for_api.rb diff --git a/app/controllers/api/api_base_controller.rb b/app/controllers/api/api_base_controller.rb index 57e3d216..d85692bc 100644 --- a/app/controllers/api/api_base_controller.rb +++ b/app/controllers/api/api_base_controller.rb @@ -3,6 +3,12 @@ module Api class ApiBaseController < ActionController::API include ActiveStorage::SetCurrent + include Pagy::Backend + + DEFAULT_LIMIT = 10 + MAX_LIMIT = 100 + + wrap_parameters false before_action :authenticate_user! check_authorization @@ -17,6 +23,16 @@ module Api private + def paginate(relation) + result = relation.order(id: :desc) + .limit([params[:limit] || DEFAULT_LIMIT, MAX_LIMIT].min) + + result = result.where('id < ?', params[:after]) if params[:after].present? + result = result.where('id > ?', params[:before]) if params[:before].present? + + result + end + def current_account current_user&.account end diff --git a/app/controllers/api/submissions_controller.rb b/app/controllers/api/submissions_controller.rb index 30b85625..14abd825 100644 --- a/app/controllers/api/submissions_controller.rb +++ b/app/controllers/api/submissions_controller.rb @@ -2,12 +2,52 @@ module Api class SubmissionsController < ApiBaseController - load_and_authorize_resource :template + load_and_authorize_resource :template, only: :create + load_and_authorize_resource :submission, only: %i[show index] - before_action do + before_action only: :create do authorize!(:create, Submission) end + def index + submissions = Submissions.search(@submissions, params[:q]) + submissions = submissions.where(template_id: params[:template_id]) if params[:template_id].present? + + submissions = paginate(submissions.preload(:created_by_user, :template, :submitters)) + + render json: { + data: submissions.as_json(serialize_params), + pagination: { + count: submissions.size, + next: submissions.last&.id, + prev: submissions.first&.id + } + } + end + + def show + serialized_subbmitters = + @submission.submitters.preload(documents_attachments: :blob, attachments_attachments: :blob).map do |submitter| + Submissions::EnsureResultGenerated.call(submitter) if submitter.completed_at? + + Submitters::SerializeForApi.call(submitter) + end + + json = @submission.as_json( + serialize_params.deep_merge( + include: { + submission_events: { + only: %i[id submitter_id event_type event_timestamp] + } + } + ) + ) + + json[:submitters] = serialized_subbmitters + + render json: + end + def create is_send_email = !params[:send_email].in?(['false', false]) @@ -42,8 +82,28 @@ module Api render json: { error: e.message }, status: :unprocessable_entity end + def destroy + @submission.update!(deleted_at: Time.current) + + render json: @submission.as_json(only: %i[id deleted_at]) + end + private + def serialize_params + { + only: %i[id source submitters_order created_at updated_at], + include: { + submitters: { only: %i[id slug uuid name email phone + completed_at opened_at sent_at + created_at updated_at], + methods: %i[status] }, + template: { only: %i[id name created_at updated_at] }, + created_by_user: { only: %i[id email first_name last_name] } + } + } + end + def submissions_params params.permit(submission: [{ submitters: [[:uuid, :name, :email, :role, :completed, :phone, { values: {} }]] diff --git a/app/controllers/api/submitters_controller.rb b/app/controllers/api/submitters_controller.rb new file mode 100644 index 00000000..03a21fdd --- /dev/null +++ b/app/controllers/api/submitters_controller.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Api + class SubmittersController < ApiBaseController + load_and_authorize_resource :submitter + + def show + Submissions::EnsureResultGenerated.call(@submitter) if @submitter.completed_at? + + render json: Submitters::SerializeForApi.call(@submitter, with_template: true, with_events: true) + end + end +end diff --git a/app/controllers/api/templates_controller.rb b/app/controllers/api/templates_controller.rb index ef4a1e33..98815000 100644 --- a/app/controllers/api/templates_controller.rb +++ b/app/controllers/api/templates_controller.rb @@ -5,22 +5,51 @@ module Api load_and_authorize_resource :template def index - render json: @templates + templates = Templates.search(@templates, params[:q]) + + templates = params[:archived] ? templates.archived : templates.active + + templates = paginate(templates.preload(:author, documents_attachments: :blob)) + + render json: { + data: templates.as_json(serialize_params), + pagination: { + count: templates.size, + next: templates.last&.id, + prev: templates.first&.id + } + } end def show - render json: @template.as_json(include: { author: { only: %i[id email first_name last_name] }, - documents: { only: %i[id uuid], methods: %i[url filename] } }) + render json: @template.as_json(serialize_params) end def update + if (folder_name = params.dig(:template, :folder_name)) + @template.folder = TemplateFolders.find_or_create_by_name(current_user, folder_name) + end + @template.update!(template_params) - render :ok + render json: @template.as_json(only: %i[id updated_at]) + end + + def destroy + @template.update!(deleted_at: Time.current) + + render json: @template.as_json(only: %i[id deleted_at]) end private + def serialize_params + { + include: { author: { only: %i[id email first_name last_name] }, + documents: { only: %i[id uuid], methods: %i[url filename] } } + } + end + def template_params params.require(:template).permit(:name, schema: [%i[attachment_uuid name]], diff --git a/app/models/template.rb b/app/models/template.rb index 2168689d..e1d4fbb9 100644 --- a/app/models/template.rb +++ b/app/models/template.rb @@ -58,6 +58,7 @@ class Template < ApplicationRecord has_many :submissions, dependent: :destroy scope :active, -> { where(deleted_at: nil) } + scope :archived, -> { where.not(deleted_at: nil) } private diff --git a/app/views/api_settings/index.html.erb b/app/views/api_settings/index.html.erb index e8530140..aff07684 100644 --- a/app/views/api_settings/index.html.erb +++ b/app/views/api_settings/index.html.erb @@ -16,7 +16,7 @@
- Request signature, single submitter + Request signature, multiple submitters with default values
POST
@@ -28,8 +28,22 @@ <% text = capture do %>curl --location '<%= api_submissions_url %>' \ --header 'X-Auth-Token: <%= current_user.access_token.token %>' \ --data-raw '{ - "template_id": <%= current_account.templates.last&.id || 1 %>, - "emails": "<%= current_user.email.sub('@', '+test@') %>, <%= current_user.email.sub('@', '+test2@') %>" + "template_id": <%= current_account.templates.last&.id || 1 %>, + "submission": [ + { + "submitters": [ + { + "name": "John Doe", + "role": "<%= current_account.templates.last ? current_account.templates.last.submitters.first['name'] : 'First Submitter' %>", + "email": "<%= current_user.email.sub('@', '+test@') %>", + "values": { + "Form Text Field Name": "Default Value" + } + }, + { "role": "Second Submitter", "email": "<%= current_user.email.sub('@', '+test2@') %>" } + ] + } + ] }'<% end.to_str %> <%= render 'shared/clipboard_copy', icon: 'copy', text:, class: 'btn btn-ghost text-white', icon_class: 'w-6 h-6 text-white', copy_title: 'Copy', copied_title: 'Copied' %> @@ -42,34 +56,20 @@
- Request signature, multiple submitters with default values + Request signature, single submitter
POST
-
<%= api_submissions_path %>
+
<%= api_submissions_emails_path %>
- <% text = capture do %>curl --location '<%= api_submissions_url %>' \ + <% text = capture do %>curl --location '<%= api_submissions_emails_url %>' \ --header 'X-Auth-Token: <%= current_user.access_token.token %>' \ --data-raw '{ - "template_id": <%= current_account.templates.last&.id || 1 %>, - "submission": [ - { - "submitters": [ - { - "name": "John Doe", - "role": "<%= current_account.templates.last ? current_account.templates.last.submitters.first['name'] : 'First Submitter' %>", - "email": "<%= current_user.email.sub('@', '+test@') %>", - "values": { - "Form Text Field Name": "Default Value" - } - }, - { "name": "Second Submitter", "email": "<%= current_user.email.sub('@', '+test2@') %>" } - ] - } - ] + "template_id": <%= current_account.templates.last&.id || 1 %>, + "emails": "<%= current_user.email.sub('@', '+test@') %>, <%= current_user.email.sub('@', '+test2@') %>" }'<% end.to_str %> <%= render 'shared/clipboard_copy', icon: 'copy', text:, class: 'btn btn-ghost text-white', icon_class: 'w-6 h-6 text-white', copy_title: 'Copy', copied_title: 'Copied' %> @@ -101,5 +101,8 @@
+
+ <%= link_to 'Open Full API reference', Docuseal::PRODUCT_URL + '/docs/api', class: 'btn btn-warning text-base mt-4 px-8', target: '_blank' %> +
diff --git a/config/routes.rb b/config/routes.rb index 766a7cda..96dd15f8 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -35,9 +35,14 @@ Rails.application.routes.draw do resources :template_folders_autocomplete, only: %i[index] resources :submitter_email_clicks, only: %i[create] resources :submitter_form_views, only: %i[create] - resources :submissions, only: %i[create] - resources :templates, only: %i[update show index] do - resources :submissions, only: %i[create] + resources :submitters, only: %i[show] + resources :submissions, only: %i[index show create destroy] do + collection do + resources :emails, only: %i[create], controller: 'submissions', as: :submissions_emails + end + end + resources :templates, only: %i[update show index destroy] do + resources :submissions, only: %i[index create] resources :documents, only: %i[create], controller: 'templates_documents' end end diff --git a/lib/submitters/serialize_for_api.rb b/lib/submitters/serialize_for_api.rb new file mode 100644 index 00000000..d8566854 --- /dev/null +++ b/lib/submitters/serialize_for_api.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +module Submitters + module SerializeForApi + module_function + + def call(submitter, with_template: false, with_events: false) + ActiveRecord::Associations::Preloader.new( + records: [submitter], + associations: [documents_attachments: :blob, attachments_attachments: :blob] + ).call + + values = SerializeForWebhook.build_values_array(submitter) + documents = SerializeForWebhook.build_documents_array(submitter) + + submitter_name = (submitter.submission.template_submitters || + submitter.submission.template.submitters).find { |e| e['uuid'] == submitter.uuid }['name'] + + serialize_params = { + include: {}, + only: %i[id slug uuid name email phone completed_at + opened_at sent_at created_at updated_at] + } + + serialize_params[:include][:template] = { only: %i[id name created_at updated_at] } if with_template + + if with_events + serialize_params[:include][:submission_events] = + { as: :events, only: %i[id submitter_id event_type event_timestamp] } + end + + submitter.as_json(serialize_params) + .merge('values' => values, + 'documents' => documents, + 'role' => submitter_name) + end + end +end diff --git a/lib/submitters/serialize_for_webhook.rb b/lib/submitters/serialize_for_webhook.rb index 908db9b1..6ee2f7f9 100644 --- a/lib/submitters/serialize_for_webhook.rb +++ b/lib/submitters/serialize_for_webhook.rb @@ -5,6 +5,11 @@ module Submitters module_function def call(submitter) + ActiveRecord::Associations::Preloader.new( + records: [submitter], + associations: [documents_attachments: :blob, attachments_attachments: :blob] + ).call + values = build_values_array(submitter) documents = build_documents_array(submitter) @@ -21,7 +26,7 @@ module Submitters def build_values_array(submitter) fields_index = (submitter.submission.template_fields || submitter.submission.template.fields).index_by { |e| e['uuid'] } - attachments_index = submitter.attachments.preload(:blob).index_by(&:uuid) + attachments_index = submitter.attachments.index_by(&:uuid) submitter_field_counters = Hash.new { 0 } submitter.values.map do |uuid, value| @@ -38,7 +43,7 @@ module Submitters end def build_documents_array(submitter) - submitter.documents.preload(:blob).map do |attachment| + submitter.documents.map do |attachment| { name: attachment.filename.base, url: rails_storage_proxy_url(attachment) } end end