assinafy 1.5.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,267 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Assinafy
4
+ module Resources
5
+ # Accounts (workspaces): CRUD plus the per-account theme, KPI stats, and
6
+ # brand logo (upload/download/delete).
7
+ #
8
+ # See https://api.assinafy.com.br/v1/docs for the Account Object and its
9
+ # related endpoints.
10
+ class AccountResource < BaseResource
11
+ # List the accounts (workspaces) the authenticated user can access.
12
+ #
13
+ # @return [Hash{Symbol=>Array,nil}] `{ data: [Account, ...], meta: nil }`
14
+ # (this endpoint sends no pagination headers)
15
+ # @see GET /accounts
16
+ # @example List my accounts
17
+ # # Request: GET /accounts
18
+ # client.accounts.list
19
+ #
20
+ # # Response (unwrapped data payload):
21
+ # {
22
+ # data: [
23
+ # {
24
+ # 'id' => 'account-id',
25
+ # 'name' => 'MT',
26
+ # 'roles' => ['owner'],
27
+ # 'is_delete_allowed' => true,
28
+ # 'created_at' => '2026-05-12T18:05:11Z'
29
+ # }
30
+ # # ... (one Hash per accessible account)
31
+ # ],
32
+ # meta: nil
33
+ # }
34
+ def list
35
+ call_list('Failed to list accounts') do
36
+ http_get('accounts')
37
+ end
38
+ end
39
+
40
+ # Create a new account (workspace).
41
+ #
42
+ # @param payload [Hash]
43
+ # @option payload [String] :name required display name
44
+ # @option payload [String] :notification_sender_type `"User"` or `"Account"`
45
+ # @return [Hash] the created account (envelope `data` unwrapped)
46
+ # @see POST /accounts
47
+ # @example Create an account
48
+ # # Request: POST /accounts
49
+ # # Body: { "name": "Acme Inc." }
50
+ # client.accounts.create(name: 'Acme Inc.')
51
+ #
52
+ # # Response (unwrapped data payload):
53
+ # {
54
+ # 'id' => 'account-id',
55
+ # 'name' => 'Acme Inc.',
56
+ # 'primary_color' => nil,
57
+ # 'secondary_color' => nil,
58
+ # 'created_at' => '2026-07-20T15:53:33Z'
59
+ # }
60
+ def create(payload)
61
+ body = body_params(require_payload(payload, 'Account payload'))
62
+ require_present(body['name'], 'name')
63
+
64
+ call('Failed to create account') do
65
+ http_post('accounts', body)
66
+ end
67
+ end
68
+
69
+ # Fetch an account by ID (defaults to the client's account).
70
+ #
71
+ # @param account_id_override [String, nil]
72
+ # @return [Hash] the account (envelope `data` unwrapped)
73
+ # @see GET /accounts/{account_id}
74
+ # @example Fetch the current account
75
+ # # Request: GET /accounts/{account_id}
76
+ # client.accounts.get
77
+ #
78
+ # # Response (unwrapped data payload):
79
+ # {
80
+ # 'id' => 'account-id',
81
+ # 'name' => 'MT',
82
+ # 'primary_color' => nil,
83
+ # 'secondary_color' => nil,
84
+ # 'created_at' => '2026-05-12T18:05:11Z'
85
+ # }
86
+ def get(account_id_override = nil)
87
+ acc_id = account_id(account_id_override)
88
+
89
+ call('Failed to fetch account') do
90
+ http_get("accounts/#{acc_id}")
91
+ end
92
+ end
93
+
94
+ # Update an account.
95
+ #
96
+ # @param payload [Hash] `name` and/or `notification_sender_type`
97
+ # @param account_id_override [String, nil]
98
+ # @return [Hash] the updated account (envelope `data` unwrapped)
99
+ # @see PUT /accounts/{account_id}
100
+ # @example Rename the current account
101
+ # # Request: PUT /accounts/{account_id}
102
+ # # Body: { "name": "Acme Renamed" }
103
+ # client.accounts.update(name: 'Acme Renamed')
104
+ #
105
+ # # Response (unwrapped data payload):
106
+ # {
107
+ # 'id' => 'account-id',
108
+ # 'name' => 'Acme Renamed',
109
+ # 'primary_color' => nil,
110
+ # 'secondary_color' => nil,
111
+ # 'created_at' => '2026-07-20T15:53:33Z'
112
+ # }
113
+ def update(payload, account_id_override = nil)
114
+ acc_id = account_id(account_id_override)
115
+ body = body_params(require_payload(payload, 'Account payload'))
116
+
117
+ call('Failed to update account') do
118
+ http_put("accounts/#{acc_id}", body)
119
+ end
120
+ end
121
+
122
+ # Delete an account. Pass `force: true` to delete an account that still
123
+ # owns documents.
124
+ #
125
+ # @param force [Boolean] force deletion (default false)
126
+ # @param account_id_override [String, nil]
127
+ # @return [nil] the API returns `data: []`; the SDK normalizes this to `nil`
128
+ # @see DELETE /accounts/{account_id}
129
+ # @example Force-delete a throwaway account
130
+ # # Request: DELETE /accounts/{account_id}
131
+ # # Body: { "force": true }
132
+ # client.accounts.delete(force: true, account_id_override: 'account-id')
133
+ # # => nil
134
+ def delete(force: false, account_id_override: nil)
135
+ acc_id = account_id(account_id_override)
136
+ force = require_boolean(force, 'force')
137
+
138
+ call_void('Failed to delete account') do
139
+ http_delete("accounts/#{acc_id}", body: body_params(force: force))
140
+ end
141
+ end
142
+
143
+ # Fetch the account's public theme (name, brand colors, logo URL).
144
+ #
145
+ # @param account_id_override [String, nil]
146
+ # @return [Hash] `{ 'account_name' =>, 'primary_color' =>, 'secondary_color' =>, 'logo' => }`
147
+ # @see GET /accounts/{account_id}/theme
148
+ # @example Fetch the account theme
149
+ # # Request: GET /accounts/{account_id}/theme
150
+ # client.accounts.theme
151
+ #
152
+ # # Response (unwrapped data payload):
153
+ # {
154
+ # 'account_name' => 'MT',
155
+ # 'primary_color' => '2072b9',
156
+ # 'secondary_color' => 'ffffff',
157
+ # 'logo' => nil
158
+ # }
159
+ def theme(account_id_override = nil)
160
+ acc_id = account_id(account_id_override)
161
+
162
+ call('Failed to fetch account theme') do
163
+ http_get("accounts/#{acc_id}/theme")
164
+ end
165
+ end
166
+
167
+ # Fetch per-account document KPIs.
168
+ #
169
+ # @note Documented in the API reference but not enabled on every
170
+ # environment — the sandbox currently returns 404 for this route.
171
+ # @param granularity [String, nil] `"monthly"` or `"daily"`
172
+ # @param month [String, nil] e.g. `"2026-06"`
173
+ # @param account_id_override [String, nil]
174
+ # @return [Array<Hash>] one KPI entry per period
175
+ # @see GET /accounts/{account_id}/stats
176
+ # @example Fetch monthly KPIs
177
+ # # Request: GET /accounts/{account_id}/stats?granularity=monthly&month=2026-06
178
+ # client.accounts.stats(granularity: 'monthly', month: '2026-06')
179
+ #
180
+ # # Response (unwrapped data payload):
181
+ # [
182
+ # {
183
+ # 'period' => '2026-06',
184
+ # 'documents_uploaded' => 42,
185
+ # 'documents_sent' => 37,
186
+ # 'signature_requests' => 61,
187
+ # 'signature_requests_email' => 45,
188
+ # 'signature_requests_whatsapp' => 16,
189
+ # 'signature_requests_viewed' => 58,
190
+ # 'signature_requests_completed' => 52,
191
+ # 'documents_certified' => 34
192
+ # }
193
+ # ]
194
+ def stats(granularity: nil, month: nil, account_id_override: nil)
195
+ acc_id = account_id(account_id_override)
196
+
197
+ call('Failed to fetch account stats') do
198
+ http_get("accounts/#{acc_id}/stats", query_params(granularity: granularity, month: month))
199
+ end
200
+ end
201
+
202
+ # Download the account brand logo as raw image bytes.
203
+ #
204
+ # @param account_id_override [String, nil]
205
+ # @return [String] binary image body
206
+ # @raise [Assinafy::ApiError] when no logo is configured (HTTP 404)
207
+ # @see GET /accounts/{account_id}/logo
208
+ # @example Download the logo and save it
209
+ # # Request: GET /accounts/{account_id}/logo
210
+ # bytes = client.accounts.download_logo
211
+ # File.binwrite('logo.png', bytes)
212
+ def download_logo(account_id_override = nil)
213
+ acc_id = account_id(account_id_override)
214
+
215
+ call_binary('Failed to download account logo') do
216
+ http_get("accounts/#{acc_id}/logo")
217
+ end
218
+ end
219
+
220
+ # Upload (replace) the account brand logo.
221
+ #
222
+ # @param source [String, Hash] a path to an image, or a Hash with
223
+ # `:file_path` (path) **or** `:buffer` + `:file_name` (raw bytes).
224
+ # @param account_id_override [String, nil]
225
+ # @return [nil, Hash] `nil` for the OpenAPI's no-data envelope; the current
226
+ # sandbox returns `{ 'mime_type' =>, 'version' =>, 'updated_at' => }`
227
+ # @see POST /accounts/{account_id}/logo
228
+ # @example Upload a PNG logo
229
+ # # Request: POST /accounts/{account_id}/logo (multipart/form-data)
230
+ # # Body: file=<binary image/png>
231
+ # client.accounts.upload_logo('/path/to/logo.png')
232
+ #
233
+ # # Current sandbox response (unwrapped data payload):
234
+ # {
235
+ # 'mime_type' => 'image/png',
236
+ # 'version' => 1784562814,
237
+ # 'updated_at' => '2026-07-20T15:53:35Z'
238
+ # }
239
+ # # => nil when the API returns the documented no-data envelope
240
+ def upload_logo(source, account_id_override = nil)
241
+ acc_id = account_id(account_id_override)
242
+ buffer, file_name = read_source(source)
243
+
244
+ call('Failed to upload account logo') do
245
+ http_post("accounts/#{acc_id}/logo", { file: file_part(buffer, file_name) })
246
+ end
247
+ end
248
+
249
+ # Delete the account brand logo.
250
+ #
251
+ # @param account_id_override [String, nil]
252
+ # @return [nil] the documented success envelope has no `data` payload
253
+ # @see DELETE /accounts/{account_id}/logo
254
+ # @example Delete the logo
255
+ # # Request: DELETE /accounts/{account_id}/logo
256
+ # client.accounts.delete_logo
257
+ # # => nil
258
+ def delete_logo(account_id_override = nil)
259
+ acc_id = account_id(account_id_override)
260
+
261
+ call_void('Failed to delete account logo') do
262
+ http_delete("accounts/#{acc_id}/logo")
263
+ end
264
+ end
265
+ end
266
+ end
267
+ end