async-firefly 1.0.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.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/lib/async/firefly/about.rb +79 -0
  3. data/lib/async/firefly/accounts.rb +129 -0
  4. data/lib/async/firefly/attachments.rb +53 -0
  5. data/lib/async/firefly/autocomplete.rb +435 -0
  6. data/lib/async/firefly/available_budgets.rb +59 -0
  7. data/lib/async/firefly/bills.rb +107 -0
  8. data/lib/async/firefly/budgets.rb +165 -0
  9. data/lib/async/firefly/categories.rb +63 -0
  10. data/lib/async/firefly/charts.rb +73 -0
  11. data/lib/async/firefly/client.rb +89 -0
  12. data/lib/async/firefly/configurations.rb +62 -0
  13. data/lib/async/firefly/currencies.rb +71 -0
  14. data/lib/async/firefly/data.rb +37 -0
  15. data/lib/async/firefly/error.rb +57 -0
  16. data/lib/async/firefly/escape.rb +9 -0
  17. data/lib/async/firefly/exchange_rates.rb +77 -0
  18. data/lib/async/firefly/insight.rb +185 -0
  19. data/lib/async/firefly/links.rb +69 -0
  20. data/lib/async/firefly/nested.rb +75 -0
  21. data/lib/async/firefly/object_groups.rb +33 -0
  22. data/lib/async/firefly/page.rb +139 -0
  23. data/lib/async/firefly/piggy_banks.rb +153 -0
  24. data/lib/async/firefly/preferences.rb +58 -0
  25. data/lib/async/firefly/recurrences.rb +169 -0
  26. data/lib/async/firefly/redacted.rb +32 -0
  27. data/lib/async/firefly/representation.rb +99 -0
  28. data/lib/async/firefly/rule_groups.rb +55 -0
  29. data/lib/async/firefly/rules.rb +105 -0
  30. data/lib/async/firefly/search.rb +40 -0
  31. data/lib/async/firefly/submit.rb +139 -0
  32. data/lib/async/firefly/summary.rb +45 -0
  33. data/lib/async/firefly/sums.rb +17 -0
  34. data/lib/async/firefly/tags.rb +57 -0
  35. data/lib/async/firefly/transactions.rb +231 -0
  36. data/lib/async/firefly/user_groups.rb +49 -0
  37. data/lib/async/firefly/users.rb +35 -0
  38. data/lib/async/firefly/version.rb +5 -0
  39. data/lib/async/firefly/webhooks.rb +101 -0
  40. data/lib/async/firefly/wrapper.rb +65 -0
  41. data/lib/async/firefly.rb +2 -0
  42. data/license.md +21 -0
  43. data/readme.md +498 -0
  44. metadata +110 -0
@@ -0,0 +1,40 @@
1
+ require_relative "page"
2
+
3
+ module Async
4
+ module Firefly
5
+ class SearchAccountPage < Page
6
+ MEMBER = Account
7
+
8
+ def member_path(record) = "../../accounts/" + self.identifier(record, :id)
9
+ end
10
+
11
+ class SearchTransactionPage < Page
12
+ def member_class = Transaction
13
+
14
+ def member_path(record) = "../../transactions/" + self.identifier(record, :id)
15
+ end
16
+
17
+ module Search
18
+ def search_accounts(query:, field:, type: nil, **parameters) = Collection.for(self, "v1/search/accounts", SearchAccountPage, **{query:, field:, type:}.compact, **parameters)
19
+
20
+ def search_transactions(query:, **parameters) = Collection.for(self, "v1/search/transactions", SearchTransactionPage, query:, **parameters)
21
+
22
+ def count_transactions(external_identifier: nil, internal_reference: nil, notes: nil, description: nil, include_deleted: nil)
23
+ resource = self.with(path: "v1/search/transactions/count", parameters: {external_identifier:, internal_reference:, notes:, description:, include_deleted:}.compact)
24
+
25
+ Representation.get(resource) do |target, response|
26
+ unless response.success?
27
+ raise Failure.for(response)
28
+ end
29
+
30
+ case response.read
31
+ in {count: Integer => count}
32
+ count
33
+ else
34
+ nil
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,139 @@
1
+ require_relative "error"
2
+
3
+ module Async
4
+ module Firefly
5
+ module Submit
6
+ TOO_MANY_REQUESTS = 429
7
+
8
+ MAXIMUM_ATTEMPTS = 4
9
+
10
+ MAXIMUM_DELAY = 30.0
11
+
12
+ def submit(klass, resource, payload = nil, method: :post, through: nil, identify: nil)
13
+ carrier = through || klass
14
+
15
+ self.with_retry do
16
+ carrier.public_send(method, resource, payload) do |target, response|
17
+ unless response.success?
18
+ raise Failure.for(response)
19
+ end
20
+
21
+ self.representation_of(klass, target, response, identify)
22
+ end
23
+ end
24
+ end
25
+
26
+ def fetch(klass, resource, &block)
27
+ self.with_retry do
28
+ klass.get(resource) do |target, response|
29
+ unless response.success?
30
+ raise Failure.for(response)
31
+ end
32
+
33
+ if block
34
+ block.call(target, response)
35
+ else
36
+ klass.new(target, value: response.read, metadata: response.headers)
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ def representation_of(klass, resource, response, identify)
43
+ value = response.read
44
+
45
+ klass.new(self.identified(resource, identify, value), value: value, metadata: response.headers)
46
+ end
47
+
48
+ def identified(resource, identify, value)
49
+ id = self.identifier_of(value, identify)
50
+
51
+ if identify.nil? || id.nil?
52
+ resource
53
+ else
54
+ resource.with(path: self.prefix_of(identify) + Firefly.escape(id), parameters: nil, merge: false)
55
+ end
56
+ end
57
+
58
+ def identifier_of(value, identify)
59
+ if identify.is_a?(Symbol)
60
+ self.attribute_of(value, identify)
61
+ else
62
+ self.record_identifier_of(value)
63
+ end
64
+ end
65
+
66
+ def record_identifier_of(value)
67
+ case value
68
+ in {data: {id: String | Integer => id}}
69
+ id
70
+ else
71
+ nil
72
+ end
73
+ end
74
+
75
+ def attribute_of(value, name)
76
+ case value
77
+ in {data: {attributes: Hash => attributes}}
78
+ attributes[name]
79
+ in {data: Hash => data}
80
+ data[name]
81
+ else
82
+ nil
83
+ end
84
+ end
85
+
86
+ def prefix_of(identify)
87
+ if identify == true || identify.is_a?(Symbol)
88
+ ""
89
+ else
90
+ identify
91
+ end
92
+ end
93
+
94
+ def upload(klass, resource, bytes, method: :post)
95
+ self.submit(klass, resource, bytes, method: method, through: Upload)
96
+ end
97
+
98
+ def discard(resource, payload = nil, method: :delete)
99
+ self.with_retry do
100
+ Representation.public_send(method, resource, payload) do |target, response|
101
+ unless response.success?
102
+ raise Failure.for(response)
103
+ end
104
+
105
+ true
106
+ end
107
+ end
108
+ end
109
+
110
+ def with_retry(attempts: MAXIMUM_ATTEMPTS, &block)
111
+ attempt = 1
112
+
113
+ while true
114
+ begin
115
+ break block.call
116
+ rescue Failure => failure
117
+ if attempt >= attempts
118
+ raise
119
+ end
120
+
121
+ unless failure.status == TOO_MANY_REQUESTS
122
+ raise
123
+ end
124
+
125
+ delay = failure.retry_after.to_f
126
+
127
+ if delay <= 0.0 || delay > MAXIMUM_DELAY
128
+ raise
129
+ end
130
+
131
+ attempt += 1
132
+
133
+ sleep(delay)
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,45 @@
1
+ require_relative "page"
2
+
3
+ module Async
4
+ module Firefly
5
+ class BasicSummaryEntry < Representation
6
+ def key = self.value[:key]
7
+
8
+ def title = self.value[:title]
9
+
10
+ def monetary_value = self.value[:monetary_value]
11
+
12
+ def currency_id = self.value[:currency_id]
13
+
14
+ def currency_code = self.value[:currency_code]
15
+
16
+ def currency_symbol = self.value[:currency_symbol]
17
+
18
+ def currency_decimal_places = self.value[:currency_decimal_places]
19
+
20
+ def no_available_budgets = self.value[:no_available_budgets]
21
+
22
+ def value_parsed = self.value[:value_parsed]
23
+
24
+ def sub_title = self.value[:sub_title]
25
+ end
26
+
27
+ class BasicSummary < Representation
28
+ include Enumerable
29
+
30
+ def each(&block)
31
+ if block
32
+ self.deconstruct_keys(nil).each do |_, entry|
33
+ block.call(BasicSummaryEntry.new(@resource, value: entry, metadata: self.metadata))
34
+ end
35
+ else
36
+ self.to_enum(:each)
37
+ end
38
+ end
39
+ end
40
+
41
+ module Summary
42
+ def basic_summary(**parameters) = self.fetch(BasicSummary, self.with(path: "v1/summary/basic", parameters: parameters))
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,17 @@
1
+ require_relative "representation"
2
+
3
+ module Async
4
+ module Firefly
5
+ class ArrayEntryWithCurrencyAndSum < Representation
6
+ def currency_id = self.value[:currency_id]
7
+
8
+ def currency_code = self.value[:currency_code]
9
+
10
+ def currency_symbol = self.value[:currency_symbol]
11
+
12
+ def currency_decimal_places = self.value[:currency_decimal_places]
13
+
14
+ def sum = self.value[:sum]
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,57 @@
1
+ require_relative "page"
2
+
3
+ module Async
4
+ module Firefly
5
+ class Tag < Representation
6
+ def created_at = self.attributes[:created_at]
7
+
8
+ def updated_at = self.attributes[:updated_at]
9
+
10
+ def tag = self.attributes[:tag]
11
+
12
+ def date = self.attributes[:date]
13
+
14
+ def description = self.attributes[:description]
15
+
16
+ def latitude = self.attributes[:latitude]
17
+
18
+ def longitude = self.attributes[:longitude]
19
+
20
+ def zoom_level = self.attributes[:zoom_level]
21
+
22
+ def attachments(**parameters) = Collection.for(@resource, "attachments", TagAttachmentPage, **parameters)
23
+
24
+ def transactions(**parameters) = Collection.for(@resource, "transactions", TagTransactionPage, **parameters)
25
+
26
+ def update(payload) = self.submit(Tag, @resource, payload, method: :put)
27
+
28
+ def delete = self.discard(@resource)
29
+ end
30
+
31
+ class TagPage < Page
32
+ MEMBER = Tag
33
+
34
+ def member_path(record) = self.identifier(record, :attributes, :tag)
35
+ end
36
+
37
+ class TagAttachmentPage < NestedPage
38
+ def member_class = Attachment
39
+
40
+ def collection = "attachments"
41
+ end
42
+
43
+ class TagTransactionPage < NestedPage
44
+ def member_class = Transaction
45
+
46
+ def collection = "transactions"
47
+ end
48
+
49
+ module Tags
50
+ def tags(**parameters) = Collection.for(self, "v1/tags", TagPage, **parameters)
51
+
52
+ def tag(name) = Tag.new(self.with(path: "v1/tags/" + Firefly.escape(name)))
53
+
54
+ def create_tag(payload) = self.submit(Tag, self.with(path: "v1/tags"), payload, identify: :tag)
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,231 @@
1
+ require_relative "page"
2
+
3
+ module Async
4
+ module Firefly
5
+ class TransactionSplit < Representation
6
+ def user = self.value[:user]
7
+
8
+ def transaction_journal_id = self.value[:transaction_journal_id]
9
+
10
+ def type = self.value[:type]
11
+
12
+ def date = self.value[:date]
13
+
14
+ def order = self.value[:order]
15
+
16
+ def object_has_currency_setting = self.value[:object_has_currency_setting]
17
+
18
+ def currency_id = self.value[:currency_id]
19
+
20
+ def currency_code = self.value[:currency_code]
21
+
22
+ def currency_symbol = self.value[:currency_symbol]
23
+
24
+ def currency_name = self.value[:currency_name]
25
+
26
+ def currency_decimal_places = self.value[:currency_decimal_places]
27
+
28
+ def foreign_currency_id = self.value[:foreign_currency_id]
29
+
30
+ def foreign_currency_code = self.value[:foreign_currency_code]
31
+
32
+ def foreign_currency_symbol = self.value[:foreign_currency_symbol]
33
+
34
+ def foreign_currency_decimal_places = self.value[:foreign_currency_decimal_places]
35
+
36
+ def primary_currency_id = self.value[:primary_currency_id]
37
+
38
+ def primary_currency_code = self.value[:primary_currency_code]
39
+
40
+ def primary_currency_symbol = self.value[:primary_currency_symbol]
41
+
42
+ def primary_currency_decimal_places = self.value[:primary_currency_decimal_places]
43
+
44
+ def amount = self.value[:amount]
45
+
46
+ def pc_amount = self.value[:pc_amount]
47
+
48
+ def foreign_amount = self.value[:foreign_amount]
49
+
50
+ def pc_foreign_amount = self.value[:pc_foreign_amount]
51
+
52
+ def source_balance_after = self.value[:source_balance_after]
53
+
54
+ def pc_source_balance_after = self.value[:pc_source_balance_after]
55
+
56
+ def destination_balance_after = self.value[:destination_balance_after]
57
+
58
+ def pc_destination_balance_after = self.value[:pc_destination_balance_after]
59
+
60
+ def description = self.value[:description]
61
+
62
+ def source_id = self.value[:source_id]
63
+
64
+ def source_name = self.value[:source_name]
65
+
66
+ def source_iban = self.value[:source_iban]
67
+
68
+ def source_type = self.value[:source_type]
69
+
70
+ def destination_id = self.value[:destination_id]
71
+
72
+ def destination_name = self.value[:destination_name]
73
+
74
+ def destination_iban = self.value[:destination_iban]
75
+
76
+ def destination_type = self.value[:destination_type]
77
+
78
+ def budget_id = self.value[:budget_id]
79
+
80
+ def budget_name = self.value[:budget_name]
81
+
82
+ def category_id = self.value[:category_id]
83
+
84
+ def category_name = self.value[:category_name]
85
+
86
+ def bill_id = self.value[:bill_id]
87
+
88
+ def bill_name = self.value[:bill_name]
89
+
90
+ def subscription_id = self.value[:subscription_id]
91
+
92
+ def subscription_name = self.value[:subscription_name]
93
+
94
+ def reconciled = self.value[:reconciled]
95
+
96
+ def notes = self.value[:notes]
97
+
98
+ def tags = self.value[:tags]
99
+
100
+ def internal_reference = self.value[:internal_reference]
101
+
102
+ def external_id = self.value[:external_id]
103
+
104
+ def external_url = self.value[:external_url]
105
+
106
+ def original_source = self.value[:original_source]
107
+
108
+ def recurrence_id = self.value[:recurrence_id]
109
+
110
+ def recurrence_total = self.value[:recurrence_total]
111
+
112
+ def recurrence_count = self.value[:recurrence_count]
113
+
114
+ def import_hash_v2 = self.value[:import_hash_v2]
115
+
116
+ def sepa_cc = self.value[:sepa_cc]
117
+
118
+ def sepa_ct_op = self.value[:sepa_ct_op]
119
+
120
+ def sepa_ct_id = self.value[:sepa_ct_id]
121
+
122
+ def sepa_db = self.value[:sepa_db]
123
+
124
+ def sepa_country = self.value[:sepa_country]
125
+
126
+ def sepa_ep = self.value[:sepa_ep]
127
+
128
+ def sepa_ci = self.value[:sepa_ci]
129
+
130
+ def sepa_batch_id = self.value[:sepa_batch_id]
131
+
132
+ def interest_date = self.value[:interest_date]
133
+
134
+ def book_date = self.value[:book_date]
135
+
136
+ def process_date = self.value[:process_date]
137
+
138
+ def due_date = self.value[:due_date]
139
+
140
+ def payment_date = self.value[:payment_date]
141
+
142
+ def invoice_date = self.value[:invoice_date]
143
+
144
+ def latitude = self.value[:latitude]
145
+
146
+ def longitude = self.value[:longitude]
147
+
148
+ def zoom_level = self.value[:zoom_level]
149
+
150
+ def has_attachments = self.value[:has_attachments]
151
+ end
152
+
153
+ class Transaction < Representation
154
+ def created_at = self.attributes[:created_at]
155
+
156
+ def updated_at = self.attributes[:updated_at]
157
+
158
+ def user = self.attributes[:user]
159
+
160
+ def group_title = self.attributes[:group_title]
161
+
162
+ def transactions
163
+ Array(self.attributes[:transactions]).map do |split|
164
+ TransactionSplit.new(@resource, value: split, metadata: self.metadata)
165
+ end
166
+ end
167
+
168
+ def update(payload) = self.submit(Transaction, @resource, payload, method: :put)
169
+
170
+ def delete = self.discard(@resource)
171
+
172
+ def attachments(**parameters) = Collection.for(@resource, "attachments", TransactionAttachmentPage, **parameters)
173
+
174
+ def piggy_bank_events(**parameters) = Collection.for(@resource, "piggy-bank-events", TransactionPiggyBankEventPage, **parameters)
175
+ end
176
+
177
+ class TransactionJournal < Representation
178
+ def self.schema = "Transaction"
179
+
180
+ def created_at = self.attributes[:created_at]
181
+
182
+ def updated_at = self.attributes[:updated_at]
183
+
184
+ def user = self.attributes[:user]
185
+
186
+ def group_title = self.attributes[:group_title]
187
+
188
+ def transactions
189
+ Array(self.attributes[:transactions]).map do |split|
190
+ TransactionSplit.new(@resource, value: split, metadata: self.metadata)
191
+ end
192
+ end
193
+
194
+ def delete = self.discard(@resource)
195
+
196
+ def links(**parameters) = Collection.for(@resource, "links", TransactionJournalLinkPage, **parameters)
197
+ end
198
+
199
+ class TransactionPage < Page
200
+ MEMBER = Transaction
201
+ end
202
+
203
+ class TransactionAttachmentPage < Page
204
+ MEMBER = Attachment
205
+
206
+ def member_path(record) = "../../../attachments/" + self.identifier(record, :id)
207
+ end
208
+
209
+ class TransactionPiggyBankEventPage < Page
210
+ MEMBER = PiggyBankEvent
211
+
212
+ def member_resource(record) = @resource
213
+ end
214
+
215
+ class TransactionJournalLinkPage < Page
216
+ MEMBER = TransactionLink
217
+
218
+ def member_path(record) = "../../../transaction-links/" + self.identifier(record, :id)
219
+ end
220
+
221
+ module Transactions
222
+ def transactions(**parameters) = Collection.for(self, "v1/transactions", TransactionPage, **parameters)
223
+
224
+ def transaction(id) = Transaction.new(self.with(path: "v1/transactions/" + Firefly.escape(id)))
225
+
226
+ def create_transaction(payload) = self.submit(Transaction, self.with(path: "v1/transactions"), payload, identify: true)
227
+
228
+ def transaction_journal(id) = TransactionJournal.new(self.with(path: "v1/transaction-journals/" + Firefly.escape(id)))
229
+ end
230
+ end
231
+ end
@@ -0,0 +1,49 @@
1
+ require_relative "page"
2
+
3
+ module Async
4
+ module Firefly
5
+ class UserGroupReadMembers < Representation
6
+ def user_id = self.value[:user_id]
7
+
8
+ def user_email = self.value[:user_email]
9
+
10
+ def you = self.value[:you]
11
+
12
+ def roles = self.value[:roles]
13
+ end
14
+
15
+ class UserGroup < Representation
16
+ def created_at = self.attributes[:created_at]
17
+
18
+ def updated_at = self.attributes[:updated_at]
19
+
20
+ def in_use = self.attributes[:in_use]
21
+
22
+ def can_see_members = self.attributes[:can_see_members]
23
+
24
+ def title = self.attributes[:title]
25
+
26
+ def primary_currency_id = self.attributes[:primary_currency_id]
27
+
28
+ def primary_currency_code = self.attributes[:primary_currency_code]
29
+
30
+ def primary_currency_symbol = self.attributes[:primary_currency_symbol]
31
+
32
+ def primary_currency_decimal_places = self.attributes[:primary_currency_decimal_places]
33
+
34
+ def members = Array(self.attributes[:members]).map{|member| UserGroupReadMembers.new(@resource, value: member, metadata: self.metadata)}
35
+
36
+ def update(payload) = self.submit(UserGroup, @resource, payload, method: :put)
37
+ end
38
+
39
+ class UserGroupPage < Page
40
+ MEMBER = UserGroup
41
+ end
42
+
43
+ module UserGroups
44
+ def user_groups(**parameters) = Collection.for(self, "v1/user-groups", UserGroupPage, **parameters)
45
+
46
+ def user_group(id) = UserGroup.new(self.with(path: "v1/user-groups/" + Firefly.escape(id)))
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,35 @@
1
+ require_relative "page"
2
+
3
+ module Async
4
+ module Firefly
5
+ class User < Representation
6
+ def created_at = self.attributes[:created_at]
7
+
8
+ def updated_at = self.attributes[:updated_at]
9
+
10
+ def email = self.attributes[:email]
11
+
12
+ def blocked = self.attributes[:blocked]
13
+
14
+ def blocked_code = self.attributes[:blocked_code]
15
+
16
+ def role = self.attributes[:role]
17
+
18
+ def update(payload) = self.submit(User, @resource, payload, method: :put)
19
+
20
+ def delete = self.discard(@resource)
21
+ end
22
+
23
+ class UserPage < Page
24
+ MEMBER = User
25
+ end
26
+
27
+ module Users
28
+ def users(**parameters) = Collection.for(self, "v1/users", UserPage, **parameters)
29
+
30
+ def user(id) = User.new(self.with(path: "v1/users/" + Firefly.escape(id)))
31
+
32
+ def create_user(payload) = self.submit(User, self.with(path: "v1/users"), payload, identify: true)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,5 @@
1
+ module Async
2
+ module Firefly
3
+ VERSION = "1.0.0"
4
+ end
5
+ end