active_call-zoho_sign 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,180 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ZohoSign::BaseService < ActiveCall::Base
4
+ self.abstract_class = true
5
+
6
+ CACHE_KEY = { access_token: 'zoho_sign/base_service/access_token' }.freeze
7
+
8
+ EXCEPTION_MAPPING = {
9
+ bad_request: ZohoSign::BadRequestError,
10
+ unauthorized: ZohoSign::UnauthorizedError,
11
+ forbidden: ZohoSign::ForbiddenError,
12
+ not_found: ZohoSign::NotFoundError,
13
+ proxy_authentication_required: ZohoSign::ProxyAuthenticationRequiredError,
14
+ request_timeout: ZohoSign::RequestTimeoutError,
15
+ conflict: ZohoSign::ConflictError,
16
+ unprocessable_entity: ZohoSign::UnprocessableEntityError,
17
+ too_many_requests: ZohoSign::TooManyRequestsError,
18
+ client_error: ZohoSign::ClientError,
19
+ server_error: ZohoSign::ServerError
20
+ }.freeze
21
+
22
+ config_accessor :base_url, default: 'https://sign.zoho.com/api/v1', instance_writer: false
23
+ config_accessor :cache, default: ActiveSupport::Cache::MemoryStore.new, instance_writer: false
24
+ config_accessor :logger, default: Logger.new($stdout), instance_writer: false
25
+ config_accessor :log_level, default: :info, instance_writer: false
26
+ config_accessor :log_headers, default: false, instance_writer: false
27
+ config_accessor :log_bodies, default: false, instance_writer: false
28
+ config_accessor :client_id, :client_secret, :refresh_token, instance_writer: false
29
+
30
+ attr_reader :facade
31
+
32
+ validate on: :response do
33
+ throw :abort if response.is_a?(Enumerable)
34
+
35
+ errors.add(:base, :server_error) and throw :abort if response.status >= 500
36
+
37
+ errors.add(:base, :unauthorized) and throw :abort if unauthorized?
38
+ errors.add(:base, :forbidden) and throw :abort if forbidden?
39
+ errors.add(:base, :not_found) and throw :abort if not_found?
40
+ errors.add(:base, :proxy_authentication_required) and throw :abort if proxy_authentication_required?
41
+ errors.add(:base, :request_timeout) and throw :abort if request_timeout?
42
+ errors.add(:base, :conflict) and throw :abort if conflict?
43
+ errors.add(:base, :unprocessable_entity) and throw :abort if unprocessable_entity?
44
+ errors.add(:base, :too_many_requests) and throw :abort if too_many_requests?
45
+
46
+ # Check for bad_request here since too_many_requests also has a status of 400.
47
+ errors.add(:base, :bad_request) and throw :abort if bad_request?
48
+
49
+ # We'll use client_error for everything else that we don't have an explicit exception class for.
50
+ errors.add(:base, :client_error) and throw :abort if response.status >= 400
51
+ end
52
+
53
+ class << self
54
+ def call!(...)
55
+ super
56
+ rescue ActiveCall::ValidationError => e
57
+ raise zoho_sign_validation_error(e)
58
+ rescue ActiveCall::RequestError => e
59
+ raise zoho_sign_request_error(e)
60
+ end
61
+
62
+ def zoho_sign_validation_error(exception)
63
+ ZohoSign::ValidationError.new(exception.errors, exception.message)
64
+ end
65
+
66
+ def zoho_sign_request_error(exception)
67
+ exception_for(exception.response, exception.errors, exception.message)
68
+ end
69
+
70
+ def exception_for(response, errors, message = nil)
71
+ exception_type = errors.details[:base].first[:error]
72
+
73
+ case exception_type
74
+ when *EXCEPTION_MAPPING.keys
75
+ EXCEPTION_MAPPING[exception_type].new(response, errors, message)
76
+ else
77
+ ZohoSign::RequestError.new(response, errors, message)
78
+ end
79
+ end
80
+ end
81
+
82
+ private
83
+
84
+ delegate :exception_for, to: :class
85
+
86
+ def connection
87
+ @_connection ||= Faraday.new do |conn|
88
+ conn.url_prefix = base_url
89
+ conn.request :authorization, 'Zoho-oauthtoken', -> { access_token }
90
+ conn.request :multipart
91
+ conn.request :url_encoded
92
+ conn.request :retry
93
+ conn.response :json
94
+ conn.response :logger, logger, **logger_options do |logger|
95
+ logger.filter(/(Authorization:).*"(.+)."/i, '\1 [FILTERED]')
96
+ end
97
+ conn.adapter Faraday.default_adapter
98
+ end
99
+ end
100
+
101
+ def logger_options
102
+ {
103
+ headers: log_headers,
104
+ log_level: log_level,
105
+ bodies: log_bodies,
106
+ formatter: Faraday::Logging::ColorFormatter, prefix: { request: 'ZohoSign', response: 'ZohoSign' }
107
+ }
108
+ end
109
+
110
+ def access_token
111
+ access_token = ENV['ZOHO_SIGN_ACCESS_TOKEN'].presence || cache.read(CACHE_KEY[:access_token])
112
+ return access_token if access_token.present?
113
+
114
+ service = ZohoSign::AccessToken::GetService.call
115
+ cache.fetch(CACHE_KEY[:access_token], expires_in: [service.expires_in - 10, 0].max) { service.facade.access_token }
116
+ end
117
+
118
+ def bad_request?
119
+ response.status == 400
120
+ end
121
+
122
+ def unauthorized?
123
+ response.status == 401
124
+ end
125
+
126
+ def forbidden?
127
+ response.status == 403
128
+ return false unless response.status == 400 && response.body.key?('code')
129
+
130
+ # 4003: Access to view the document is denied.
131
+ return true if [4003].include?(response.body['code'])
132
+
133
+ false
134
+ end
135
+
136
+ def not_found?
137
+ return true if response.status == 404
138
+ return false unless response.status == 400 && response.body.key?('code')
139
+
140
+ # 4066: Invalid Request ID.
141
+ # 20405: Invalid Template ID.
142
+ return true if [4066, 20405].include?(response.body['code'])
143
+
144
+ false
145
+ end
146
+
147
+ # TODO: Confirm.
148
+ def proxy_authentication_required?
149
+ response.status == 407
150
+ end
151
+
152
+ # TODO: Confirm.
153
+ def request_timeout?
154
+ response.status == 408
155
+ end
156
+
157
+ # TODO: Confirm.
158
+ def conflict?
159
+ response.status == 409
160
+ end
161
+
162
+ def unprocessable_entity?
163
+ return true if response.status == 422
164
+ return false unless response.status == 400 && response.body.key?('code')
165
+
166
+ # 4021: Something went wrong. One or more fields contain errors.
167
+ # 9009: ...
168
+ # 9043: Extra key found.
169
+ # 9056: Action array size mismatch from the templates.
170
+ return true if [4021, 9043, 9009, 9056].include?(response.body['code'])
171
+
172
+ false
173
+ end
174
+
175
+ def too_many_requests?
176
+ return false unless response.status == 400 && response.body.key?('error_description')
177
+
178
+ response.body['error_description'].include?('too many requests')
179
+ end
180
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ZohoSign::CurrentUser::Facade
4
+ attr_reader :account_id, :account_type, :api_credits_left, :api_documents_used, :automation_limit,
5
+ :automation_used_count, :branding, :configurations, :country, :documents_used, :enable_old_viewer_page, :features,
6
+ :first_name, :iam_photo_url, :is_admin, :is_api_account, :is_cloud_signing_allowed, :is_owner, :is_sign_trial,
7
+ :is_trial, :is_visible_sign_option_allowed, :is_zoho_one, :language, :last_name, :license_type, :logo_url,
8
+ :monthly_bulk_used, :monthly_signforms_used, :multiportal_enabled, :no_of_documents, :org_name, :organizations,
9
+ :payment_url, :photo_url, :plan_edition, :plan_group, :profile, :profile_details, :send_mail_from,
10
+ :show_api_warning_msg, :smtp_configured, :user_email, :user_id, :visible_sign_settings, :zo_users_url, :zooid,
11
+ :zuid, :zsoid
12
+
13
+ def initialize(hash)
14
+ @account_id = hash['account_id']
15
+ @account_type = hash['account_type']
16
+ @api_credits_left = hash['api_credits_left']
17
+ @api_documents_used = hash['api_documents_used']
18
+ @automation_limit = hash['automation_limit']
19
+ @automation_used_count = hash['automation_used_count']
20
+ @branding = hash['branding']
21
+ @configurations = hash['configurations']
22
+ @country = hash['country']
23
+ @documents_used = hash['documents_used']
24
+ @enable_old_viewer_page = hash['enable_old_viewer_page']
25
+ @features = hash['features']
26
+ @first_name = hash['first_name']
27
+ @iam_photo_url = hash['IAM_photo_url']
28
+ @is_admin = hash['is_admin']
29
+ @is_api_account = hash['is_api_account']
30
+ @is_cloud_signing_allowed = hash['is_cloud_signing_allowed']
31
+ @is_owner = hash['is_owner']
32
+ @is_sign_trial = hash['is_sign_trial']
33
+ @is_trial = hash['is_trial']
34
+ @is_visible_sign_option_allowed = hash['is_visible_sign_option_allowed']
35
+ @is_zoho_one = hash['is_zoho_one']
36
+ @language = hash['language']
37
+ @last_name = hash['last_name']
38
+ @license_type = hash['license_type']
39
+ @logo_url = hash['logo_url']
40
+ @monthly_bulk_used = hash['monthly_bulk_used']
41
+ @monthly_signforms_used = hash['monthly_signforms_used']
42
+ @multiportal_enabled = hash['multiportal_enabled']
43
+ @no_of_documents = hash['no_of_documents']
44
+ @org_name = hash['org_name']
45
+ @organizations = hash['organizations']
46
+ @payment_url = hash['payment_url']
47
+ @photo_url = hash['photo_url']
48
+ @plan_edition = hash['plan_edition']
49
+ @plan_group = hash['plan_group']
50
+ @profile = hash['profile']
51
+ @profile_details = hash['profile_details']
52
+ @send_mail_from = hash['send_mail_from']
53
+ @show_api_warning_msg = hash['show_api_warning_msg']
54
+ @smtp_configured = hash['smtp_configured']
55
+ @user_email = hash['user_email']
56
+ @user_id = hash['user_id']
57
+ @visible_sign_settings = hash['visible_sign_settings']
58
+ @zo_users_url = hash['zo_users_url']
59
+ @zooid = hash['zooid']
60
+ @zuid = hash['ZUID']
61
+ @zsoid = hash['ZSOID']
62
+ end
63
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ZohoSign::CurrentUser::GetService < ZohoSign::BaseService
4
+ after_call :set_facade
5
+
6
+ delegate_missing_to :@facade
7
+
8
+ def initialize; end
9
+
10
+ # Get the current user.
11
+ #
12
+ # ==== Examples
13
+ #
14
+ # service = ZohoSign::CurrentUser::GetService.call
15
+ #
16
+ # service.success? # => true
17
+ # service.errors # => #<ActiveModel::Errors []>
18
+ #
19
+ # service.response # => #<Faraday::Response ...>
20
+ # service.response.status # => 200
21
+ # service.response.body # => {}
22
+ #
23
+ # service.facade # => #<ZohoSign::CurrentUser::Facade ...>
24
+ # service.facade.zsoid
25
+ # service.zsoid
26
+ #
27
+ # GET /api/v1/currentuser
28
+ def call
29
+ connection.get('currentuser')
30
+ end
31
+
32
+ private
33
+
34
+ def set_facade
35
+ @facade = ZohoSign::CurrentUser::Facade.new(response.body['users'])
36
+ end
37
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ZohoSign::Document::Action::EmbedToken::Facade
4
+ attr_reader :sign_url
5
+
6
+ def initialize(hash)
7
+ @sign_url = hash['sign_url']
8
+ end
9
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ZohoSign::Document::Action::EmbedToken::GetService < ZohoSign::BaseService
4
+ attr_reader :id, :request_id, :host
5
+
6
+ after_call :set_facade
7
+
8
+ delegate_missing_to :@facade
9
+
10
+ validates :id, :request_id, :host, presence: true
11
+
12
+ def initialize(id:, request_id:, host:)
13
+ @id = id
14
+ @request_id = request_id
15
+ @host = host
16
+ end
17
+
18
+ # Get a document.
19
+ #
20
+ # ==== Examples
21
+ #
22
+ # service = ZohoSign::Document::Action::EmbedToken::GetService.call(id: '', request_id: '', host: '')
23
+ #
24
+ # service.success? # => true
25
+ # service.errors # => #<ActiveModel::Errors []>
26
+ #
27
+ # service.response # => #<Faraday::Response ...>
28
+ # service.response.status # => 200
29
+ # service.response.body # => {}
30
+ #
31
+ # service.facade # => #<ZohoSign::Document::Action::EmbedToken::Facade ...>
32
+ # service.facade.request_name
33
+ # service.request_name
34
+ #
35
+ # POST /api/v1/requests/:request_id/actions/:id/embedtoken
36
+ def call
37
+ connection.post("/api/v1/requests/#{request_id}/actions/#{id}/embedtoken", **params)
38
+ end
39
+
40
+ private
41
+
42
+ def params
43
+ {
44
+ host: host
45
+ }
46
+ end
47
+
48
+ def set_facade
49
+ @facade = ZohoSign::Document::Action::EmbedToken.new(response.body)
50
+ end
51
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ZohoSign::Document::CreateService < ZohoSign::BaseService
4
+ attr_reader :data, :file, :file_content_type, :file_name
5
+
6
+ validates :file, :file_content_type, :data, presence: true
7
+
8
+ after_call :set_facade
9
+
10
+ delegate_missing_to :@facade
11
+
12
+ def initialize(data:, file:, file_content_type:, file_name: nil)
13
+ @file = file
14
+ @file_content_type = file_content_type
15
+ @file_name = file_name
16
+ @data = data
17
+ end
18
+
19
+ # Create a document.
20
+ #
21
+ # ==== Examples
22
+ #
23
+ # service = ZohoSign::Document::CreateService.call(
24
+ # file: '/path/to/file.pdf', # or File.open('/path/to/file.pdf')
25
+ # file_name: 'file.pdf',
26
+ # file_content_type: 'application/pdf',
27
+ # data: {
28
+ # requests: {
29
+ # request_name: 'Name',
30
+ # is_sequential: false,
31
+ # actions: [{
32
+ # action_type: 'SIGN',
33
+ # recipient_email: 'eric.cartman@example.com',
34
+ # recipient_name: 'Eric Cartman',
35
+ # verify_recipient: true,
36
+ # verification_type: 'EMAIL'
37
+ # }]
38
+ # }
39
+ # }
40
+ # )
41
+ #
42
+ # service.success? # => true
43
+ # service.errors # => #<ActiveModel::Errors []>
44
+ #
45
+ # service.response # => #<Faraday::Response ...>
46
+ # service.response.status # => 200
47
+ # service.response.body # => {}
48
+ #
49
+ # service.facade # => #<ZohoSign::Document::Facade ...>
50
+ # service.facade.request_name
51
+ # service.request_name
52
+ #
53
+ # POST /api/v1/requests
54
+ def call
55
+ connection.post('requests', **params)
56
+ end
57
+
58
+ private
59
+
60
+ def params
61
+ {
62
+ file: Faraday::Multipart::FilePart.new(file, file_content_type, file_name),
63
+ data: data.to_json
64
+ }
65
+ end
66
+
67
+ def set_facade
68
+ @facade = ZohoSign::Document::Facade.new(response.body['requests'])
69
+ end
70
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ZohoSign::Document::DeleteService < ZohoSign::BaseService
4
+ attr_reader :id
5
+
6
+ validates :id, presence: true
7
+
8
+ def initialize(id:)
9
+ @id = id
10
+ end
11
+
12
+ # Delete a document.
13
+ #
14
+ # ==== Examples
15
+ #
16
+ # service = ZohoSign::Document::DeleteService.call(id: '')
17
+ #
18
+ # service.success? # => true
19
+ # service.errors # => #<ActiveModel::Errors []>
20
+ #
21
+ # service.response # => #<Faraday::Response ...>
22
+ # service.response.status # => 200
23
+ # service.response.body # => {}
24
+ #
25
+ # PUT /api/v1/requests/:id/delete
26
+ def call
27
+ connection.put("requests/#{id}/delete")
28
+ end
29
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ZohoSign::Document::Facade
4
+ attr_reader :action_time, :actions, :attachment_size, :attachments, :created_time, :description, :document_fields,
5
+ :document_ids, :email_reminders, :expire_by, :expiration_days, :folder_id, :folder_name, :in_process, :is_deleted,
6
+ :is_expiring, :is_sequential, :modified_time, :notes, :owner_email, :owner_first_name, :owner_id, :owner_last_name,
7
+ :reminder_period, :request_id, :request_name, :request_status, :request_type_id, :request_type_name, :self_sign,
8
+ :sign_percentage, :sign_submitted_time, :template_ids, :templates_used, :validity, :visible_sign_settings,
9
+ :zsdocumentid
10
+
11
+ def initialize(hash)
12
+ @action_time = hash['action_time']
13
+ @actions = hash['actions']
14
+ @attachment_size = hash['attachment_size']
15
+ @attachments = hash['attachments']
16
+ @created_time = hash['created_time']
17
+ @description = hash['description']
18
+ @document_fields = hash['document_fields']
19
+ @document_ids = hash['document_ids']
20
+ @email_reminders = hash['email_reminders']
21
+ @expire_by = hash['expire_by']
22
+ @expiration_days = hash['expiration_days']
23
+ @folder_id = hash['folder_id']
24
+ @folder_name = hash['folder_name']
25
+ @in_process = hash['in_process']
26
+ @is_deleted = hash['is_deleted']
27
+ @is_expiring = hash['is_expiring']
28
+ @is_sequential = hash['is_sequential']
29
+ @modified_time = hash['modified_time']
30
+ @notes = hash['notes']
31
+ @owner_email = hash['owner_email']
32
+ @owner_first_name = hash['owner_first_name']
33
+ @owner_id = hash['owner_id']
34
+ @owner_last_name = hash['owner_last_name']
35
+ @reminder_period = hash['reminder_period']
36
+ @request_id = hash['request_id']
37
+ @request_name = hash['request_name']
38
+ @request_status = hash['request_status']
39
+ @request_type_id = hash['request_type_id']
40
+ @request_type_name = hash['request_type_name']
41
+ @self_sign = hash['self_sign']
42
+ @sign_percentage = hash['sign_percentage']
43
+ @sign_submitted_time = hash['sign_submitted_time']
44
+ @template_ids = hash['template_ids']
45
+ @templates_used = hash['templates_used']
46
+ @validity = hash['validity']
47
+ @visible_sign_settings = hash['visible_sign_settings']
48
+ @zsdocumentid = hash['zsdocumentid']
49
+ end
50
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ZohoSign::Document::GetService < ZohoSign::BaseService
4
+ attr_reader :id
5
+
6
+ after_call :set_facade
7
+
8
+ delegate_missing_to :@facade
9
+
10
+ validates :id, presence: true
11
+
12
+ def initialize(id: nil)
13
+ @id = id
14
+ end
15
+
16
+ # Get a document.
17
+ #
18
+ # ==== Examples
19
+ #
20
+ # service = ZohoSign::Document::GetService.call(id: '')
21
+ #
22
+ # service.success? # => true
23
+ # service.errors # => #<ActiveModel::Errors []>
24
+ #
25
+ # service.response # => #<Faraday::Response ...>
26
+ # service.response.status # => 200
27
+ # service.response.body # => {}
28
+ #
29
+ # service.facade # => #<ZohoSign::Document::Facade ...>
30
+ # service.facade.request_name
31
+ # service.request_name
32
+ #
33
+ # GET /api/v1/requests/:id
34
+ def call
35
+ connection.get("requests/#{id}")
36
+ end
37
+
38
+ private
39
+
40
+ def set_facade
41
+ @facade = ZohoSign::Document::Facade.new(response.body['requests'])
42
+ end
43
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ZohoSign::Document::ListService < ZohoSign::BaseService
4
+ SORT_COLUMNS = %w[request_name folder_name owner_full_name recipient_email form_name created_time].freeze
5
+ SORT_ORDERS = %w[ASC DESC].freeze
6
+
7
+ include Enumerable
8
+
9
+ attr_reader :limit, :offset, :sort_column, :sort_order, :search_columns
10
+
11
+ validates :limit, presence: true, numericality: { greater_than_or_equal_to: 1 }
12
+ validates :sort_order, inclusion: { in: SORT_ORDERS, message: "Sort order must be one of #{SORT_ORDERS.join(', ')}" }
13
+
14
+ validates :sort_column, inclusion: {
15
+ in: SORT_COLUMNS,
16
+ message: "Sort column must be one of #{SORT_COLUMNS.join(', ')}"
17
+ }
18
+
19
+ validate do
20
+ next if search_columns.empty?
21
+ next if (SORT_COLUMNS | search_columns.keys.map(&:to_s)).size == SORT_COLUMNS.size
22
+
23
+ errors.add(:search_columns, "keys must be one of #{SORT_COLUMNS.join(', ')}")
24
+ throw :abort
25
+ end
26
+
27
+ def initialize(limit: Float::INFINITY, offset: 1, sort_column: 'created_time', sort_order: 'DESC', search_columns: {})
28
+ @limit = limit
29
+ @offset = offset
30
+ @sort_column = sort_column
31
+ @sort_order = sort_order
32
+ @search_columns = search_columns
33
+ end
34
+
35
+ # List documents.
36
+ #
37
+ # ==== Examples
38
+ #
39
+ # service = ZohoSign::Document::ListService.call.first
40
+ # service.request_name
41
+ #
42
+ # ZohoSign::Document::ListService.call(offset: 11, limit: 10).each { _1 }
43
+ # ZohoSign::Document::ListService.call(sort_column: 'recipient_email', sort_order: 'ASC').each { _1 }
44
+ # ZohoSign::Document::ListService.call(search_columns: { recipient_email: 'eric.cartman@example.com' }).each { _1 }
45
+ #
46
+ # GET /api/v1/requests
47
+ def call
48
+ self
49
+ end
50
+
51
+ def each
52
+ return to_enum(:each) unless block_given?
53
+ return if invalid?
54
+
55
+ total = 0
56
+
57
+ catch :list_end do
58
+ loop do
59
+ @response = connection.get('requests', data: params.to_json)
60
+ validate(:response)
61
+
62
+ unless success?
63
+ raise exception_for(response, errors) if bang?
64
+
65
+ throw :list_end
66
+ end
67
+
68
+ response.body['requests'].each do |hash|
69
+ yield ZohoSign::Document::Facade.new(hash)
70
+ total += 1
71
+ throw :list_end if total >= limit
72
+ end
73
+
74
+ break unless response.body.dig('page_context', 'has_more_rows')
75
+
76
+ @_params[:page_context][:start_index] += max_limit_per_request
77
+ end
78
+ end
79
+ end
80
+
81
+ private
82
+
83
+ def params
84
+ @_params ||= {
85
+ page_context: {
86
+ row_count: max_limit_per_request,
87
+ start_index: offset,
88
+ search_columns: search_columns,
89
+ sort_column: sort_column,
90
+ sort_order: sort_order
91
+ }
92
+ }
93
+ end
94
+
95
+ def max_limit_per_request
96
+ @_max_limit_per_request ||= limit.infinite? ? 100 : [limit, 100].min
97
+ end
98
+ end