add submitters api

pull/105/head
Alex Turchyn 2 years ago
parent 63e1246a11
commit 7912c9a1ee

@ -13,6 +13,7 @@ gem 'faraday'
gem 'google-cloud-storage', require: false gem 'google-cloud-storage', require: false
gem 'hexapdf' gem 'hexapdf'
gem 'image_processing' gem 'image_processing'
gem 'jwt'
gem 'lograge' gem 'lograge'
gem 'mysql2', require: false gem 'mysql2', require: false
gem 'oj' gem 'oj'

@ -540,6 +540,7 @@ DEPENDENCIES
google-cloud-storage google-cloud-storage
hexapdf hexapdf
image_processing image_processing
jwt
letter_opener_web letter_opener_web
lograge lograge
mysql2 mysql2

@ -0,0 +1,38 @@
# frozen_string_literal: true
module Api
class SubmissionsController < ApiBaseController
def create
template = current_account.templates.find(params[:template_id])
submissions =
if params[:emails].present?
Submissions.create_from_emails(template:,
user: current_user,
send_email: params[:send_email] != 'false',
emails: params[:emails])
else
Submissions.create_from_submitters(template:,
user: current_user,
send_email: params[:send_email] != 'false',
submissions_attrs: submissions_params[:submission])
end
submitters = submissions.flat_map(&:submitters)
if params[:send_email] != 'false'
submitters.each do |submitter|
SubmitterMailer.invitation_email(submitter, message: params[:message]).deliver_later!
end
end
render json: submitters
end
private
def submissions_params
params.permit(submission: [{ submitters: [%i[uuid name email]] }])
end
end
end

@ -2,6 +2,10 @@
module Api module Api
class TemplatesController < ApiBaseController class TemplatesController < ApiBaseController
def index
render json: current_account.templates
end
def update def update
@template = current_account.templates.find(params[:id]) @template = current_account.templates.find(params[:id])

@ -17,9 +17,15 @@ class SubmissionsController < ApplicationController
def create def create
submissions = submissions =
if params[:emails].present? if params[:emails].present?
create_submissions_from_emails Submissions.create_from_emails(template: @template,
user: current_user,
send_email: params[:send_email] == '1',
emails: params[:emails])
else else
create_submissions_from_submitters Submissions.create_from_submitters(template: @template,
user: current_user,
send_email: params[:send_email] == '1',
submissions_attrs: submissions_params[:submission].to_h.values)
end end
submitters = submissions.flat_map(&:submitters) submitters = submissions.flat_map(&:submitters)
@ -45,30 +51,6 @@ class SubmissionsController < ApplicationController
private private
def create_submissions_from_emails
emails = params[:emails].to_s.scan(User::EMAIL_REGEXP)
emails.map do |email|
submission = @template.submissions.new(created_by_user: current_user)
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[:submission].to_h.map do |_, attrs|
submission = @template.submissions.new(created_by_user: current_user)
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 def submissions_params
params.permit(submission: { submitters: [%i[uuid email]] }) params.permit(submission: { submitters: [%i[uuid email]] })
end end

@ -3,9 +3,9 @@
class SubmitterMailer < ApplicationMailer class SubmitterMailer < ApplicationMailer
DEFAULT_MESSAGE = %(You have been invited to submit the "%<name>s" form:) DEFAULT_MESSAGE = %(You have been invited to submit the "%<name>s" form:)
def invitation_email(submitter, message: format(DEFAULT_MESSAGE, name: submitter.submission.template.name)) def invitation_email(submitter, message: '')
@submitter = submitter @submitter = submitter
@message = message @message = message.presence || format(DEFAULT_MESSAGE, name: submitter.submission.template.name)
mail(to: @submitter.email, mail(to: @submitter.email,
subject: 'You have been invited to submit a form', subject: 'You have been invited to submit a form',

@ -22,6 +22,7 @@
# role :string not null # role :string not null
# sign_in_count :integer default(0), not null # sign_in_count :integer default(0), not null
# unlock_token :string # unlock_token :string
# uuid :text not null
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
# account_id :bigint not null # account_id :bigint not null
@ -32,6 +33,7 @@
# index_users_on_email (email) UNIQUE # index_users_on_email (email) UNIQUE
# index_users_on_reset_password_token (reset_password_token) UNIQUE # index_users_on_reset_password_token (reset_password_token) UNIQUE
# index_users_on_unlock_token (unlock_token) UNIQUE # index_users_on_unlock_token (unlock_token) UNIQUE
# index_users_on_uuid (uuid) UNIQUE
# #
# Foreign Keys # Foreign Keys
# #
@ -49,6 +51,7 @@ class User < ApplicationRecord
devise :registerable, :omniauthable, omniauth_providers: [:google_oauth2] if Docuseal.multitenant? devise :registerable, :omniauthable, omniauth_providers: [:google_oauth2] if Docuseal.multitenant?
attribute :role, :string, default: 'admin' attribute :role, :string, default: 'admin'
attribute :uuid, :string, default: -> { SecureRandom.uuid }
scope :active, -> { where(deleted_at: nil) } scope :active, -> { where(deleted_at: nil) }

@ -9,6 +9,8 @@ require 'action_view/railtie'
require 'action_mailer/railtie' require 'action_mailer/railtie'
require 'active_job/railtie' require 'active_job/railtie'
require './lib/api_path_consider_json_middleware'
Bundler.require(*Rails.groups) Bundler.require(*Rails.groups)
module DocuSeal module DocuSeal
@ -26,5 +28,6 @@ module DocuSeal
config.action_view.frozen_string_literal = true config.action_view.frozen_string_literal = true
config.middleware.insert_before ActionDispatch::Static, Rack::Deflater config.middleware.insert_before ActionDispatch::Static, Rack::Deflater
config.middleware.insert_before ActionDispatch::Static, ApiPathConsiderJsonMiddleware
end end
end end

@ -1,5 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
require './lib/auth_with_token_strategy'
Warden::Strategies.add(:auth_token, AuthWithTokenStrategy)
# Assuming you have not yet modified this file, each configuration option below # Assuming you have not yet modified this file, each configuration option below
# is set to its default value. Note that some are commented out while others # is set to its default value. Note that some are commented out while others
# are not: uncommented lines are intended to protect your configuration from # are not: uncommented lines are intended to protect your configuration from
@ -272,10 +276,10 @@ Devise.setup do |config|
# If you want to use other strategies, that are not supported by Devise, or # If you want to use other strategies, that are not supported by Devise, or
# change the failure app, you can configure them inside the config.warden block. # change the failure app, you can configure them inside the config.warden block.
# #
# config.warden do |manager| config.warden do |manager|
# manager.intercept_401 = false # manager.intercept_401 = false
# manager.default_strategies(scope: :user).unshift :some_external_strategy manager.default_strategies(scope: :user).unshift(:auth_token)
# end end
# ==> Mountable engine configurations # ==> Mountable engine configurations
# When using Devise inside an engine, let's call it `MyEngine`, and this engine # When using Devise inside an engine, let's call it `MyEngine`, and this engine

@ -27,9 +27,11 @@ Rails.application.routes.draw do
end end
end end
namespace :api do namespace :api, defaults: { format: :json } do
resources :attachments, only: %i[create] resources :attachments, only: %i[create]
resources :templates, only: %i[update] do resources :submissions, only: %i[create]
resources :templates, only: %i[update index] do
resources :submissions, only: %i[create]
resources :documents, only: %i[create destroy], controller: 'templates_documents' resources :documents, only: %i[create destroy], controller: 'templates_documents'
end end
end end

@ -0,0 +1,22 @@
# frozen_string_literal: true
class AddUuidToUsers < ActiveRecord::Migration[7.0]
class MigrationUser < ApplicationRecord
self.table_name = 'users'
end
def up
add_column :users, :uuid, :text
add_index :users, :uuid, unique: true
MigrationUser.all.each do |user|
user.update_columns(uuid: SecureRandom.uuid)
end
change_column_null :users, :uuid, false
end
def down
drop_column :users, :uuid
end
end

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.0].define(version: 2023_07_26_062428) do ActiveRecord::Schema[7.0].define(version: 2023_08_03_200825) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@ -139,10 +139,12 @@ ActiveRecord::Schema[7.0].define(version: 2023_07_26_062428) do
t.datetime "deleted_at" t.datetime "deleted_at"
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.text "uuid", null: false
t.index ["account_id"], name: "index_users_on_account_id" t.index ["account_id"], name: "index_users_on_account_id"
t.index ["email"], name: "index_users_on_email", unique: true t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
t.index ["unlock_token"], name: "index_users_on_unlock_token", unique: true t.index ["unlock_token"], name: "index_users_on_unlock_token", unique: true
t.index ["uuid"], name: "index_users_on_uuid", unique: true
end end
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"

@ -0,0 +1,17 @@
# frozen_string_literal: true
class ApiPathConsiderJsonMiddleware
def initialize(app)
@app = app
end
def call(env)
if env['PATH_INFO'].starts_with?('/api') &&
!env['PATH_INFO'].ends_with?('/documents') &&
!env['PATH_INFO'].ends_with?('/attachments')
env['CONTENT_TYPE'] = 'application/json'
end
@app.call(env)
end
end

@ -0,0 +1,21 @@
# frozen_string_literal: true
class AuthWithTokenStrategy < Devise::Strategies::Base
def valid?
request.headers['X-Auth-Token'].present?
end
def authenticate!
payload = JsonWebToken.decode(request.headers['X-Auth-Token'])
user = User.find_by(uuid: payload['uuid'])
if user
success!(user)
else
fail!('Invalid token')
end
rescue JWT::VerificationError
fail!('Invalid token')
end
end

@ -0,0 +1,13 @@
# frozen_string_literal: true
module JsonWebToken
module_function
def encode(payload)
JWT.encode(payload, Rails.application.secrets.secret_key_base)
end
def decode(token)
JWT.decode(token, Rails.application.secrets.secret_key_base)[0]
end
end

@ -10,4 +10,33 @@ module Submissions
submission.save! submission.save!
end end
def create_from_emails(template:, user:, emails:, send_email: false)
emails = emails.to_s.scan(User::EMAIL_REGEXP)
emails.map do |email|
submission = template.submissions.new(created_by_user: user)
submission.submitters.new(email:, uuid: template.submitters.first['uuid'],
sent_at: send_email ? Time.current : nil)
submission.tap(&:save!)
end
end
def create_from_submitters(template:, user:, submissions_attrs:, send_email: false)
submissions_attrs.map do |attrs|
submission = template.submissions.new(created_by_user: user)
attrs[:submitters].each_with_index do |submitter_attrs, index|
uuid =
submitter_attrs[:uuid].presence ||
template.submitters.find { |e| e['name'] == submitter_attrs[:name] }&.dig('uuid') ||
template.submitters[index]&.dig('uuid')
submission.submitters.new(**submitter_attrs, uuid:, sent_at: send_email ? Time.current : nil)
end
submission.tap(&:save!)
end
end
end end

Loading…
Cancel
Save