lunchmoney 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (142) hide show
  1. checksums.yaml +7 -0
  2. data/.DS_Store +0 -0
  3. data/.github/dependabot.yml +18 -0
  4. data/.github/workflows/build_and_publish_yard_docs.yml +47 -0
  5. data/.github/workflows/ci.yml +58 -0
  6. data/.github/workflows/dependabot-rbi-updater.yml +43 -0
  7. data/.github/workflows/publish_gem.yml +31 -0
  8. data/.gitignore +62 -0
  9. data/.rubocop.yml +45 -0
  10. data/.ruby-version +1 -0
  11. data/.toys/.toys.rb +10 -0
  12. data/.toys/ci.rb +22 -0
  13. data/.toys/rbi.rb +60 -0
  14. data/.toys/rubocop.rb +10 -0
  15. data/.toys/spoom.rb +15 -0
  16. data/.toys/typecheck.rb +5 -0
  17. data/.yardopts +2 -0
  18. data/Appraisals +22 -0
  19. data/Gemfile +25 -0
  20. data/Gemfile.lock +174 -0
  21. data/LICENSE +21 -0
  22. data/README.md +57 -0
  23. data/bin/console +16 -0
  24. data/bin/rubocop +27 -0
  25. data/bin/setup +8 -0
  26. data/bin/spoom +27 -0
  27. data/bin/srb +27 -0
  28. data/bin/tapioca +27 -0
  29. data/bin/toys +27 -0
  30. data/bin/yard +27 -0
  31. data/lib/lunchmoney/api.rb +147 -0
  32. data/lib/lunchmoney/api_call.rb +109 -0
  33. data/lib/lunchmoney/assets/asset.rb +89 -0
  34. data/lib/lunchmoney/assets/asset_calls.rb +96 -0
  35. data/lib/lunchmoney/budget/budget.rb +74 -0
  36. data/lib/lunchmoney/budget/budget_calls.rb +82 -0
  37. data/lib/lunchmoney/budget/config.rb +38 -0
  38. data/lib/lunchmoney/budget/data.rb +42 -0
  39. data/lib/lunchmoney/categories/category/category.rb +52 -0
  40. data/lib/lunchmoney/categories/category/child_category.rb +42 -0
  41. data/lib/lunchmoney/categories/category_calls.rb +195 -0
  42. data/lib/lunchmoney/configuration.rb +26 -0
  43. data/lib/lunchmoney/crypto/crypto/crypto.rb +43 -0
  44. data/lib/lunchmoney/crypto/crypto/crypto_base.rb +65 -0
  45. data/lib/lunchmoney/crypto/crypto_calls.rb +49 -0
  46. data/lib/lunchmoney/data_object.rb +25 -0
  47. data/lib/lunchmoney/errors.rb +19 -0
  48. data/lib/lunchmoney/exceptions.rb +19 -0
  49. data/lib/lunchmoney/plaid_accounts/plaid_account.rb +73 -0
  50. data/lib/lunchmoney/plaid_accounts/plaid_account_calls.rb +38 -0
  51. data/lib/lunchmoney/recurring_expenses/recurring_expense/recurring_expense.rb +65 -0
  52. data/lib/lunchmoney/recurring_expenses/recurring_expense/recurring_expense_base.rb +29 -0
  53. data/lib/lunchmoney/recurring_expenses/recurring_expense_calls.rb +28 -0
  54. data/lib/lunchmoney/tags/tag/tag.rb +20 -0
  55. data/lib/lunchmoney/tags/tag/tag_base.rb +21 -0
  56. data/lib/lunchmoney/tags/tag_calls.rb +20 -0
  57. data/lib/lunchmoney/transactions/transaction/child_transaction.rb +31 -0
  58. data/lib/lunchmoney/transactions/transaction/split.rb +24 -0
  59. data/lib/lunchmoney/transactions/transaction/transaction.rb +156 -0
  60. data/lib/lunchmoney/transactions/transaction/transaction_base.rb +52 -0
  61. data/lib/lunchmoney/transactions/transaction/transaction_modification_base.rb +30 -0
  62. data/lib/lunchmoney/transactions/transaction/update_transaction.rb +43 -0
  63. data/lib/lunchmoney/transactions/transaction_calls.rb +218 -0
  64. data/lib/lunchmoney/user/user.rb +36 -0
  65. data/lib/lunchmoney/user/user_calls.rb +19 -0
  66. data/lib/lunchmoney/validators.rb +43 -0
  67. data/lib/lunchmoney/version.rb +7 -0
  68. data/lib/lunchmoney.rb +54 -0
  69. data/lunchmoney.gemspec +34 -0
  70. data/sorbet/config +5 -0
  71. data/sorbet/rbi/annotations/.gitattributes +1 -0
  72. data/sorbet/rbi/annotations/activesupport.rbi +410 -0
  73. data/sorbet/rbi/annotations/faraday.rbi +17 -0
  74. data/sorbet/rbi/annotations/mocha.rbi +34 -0
  75. data/sorbet/rbi/annotations/rainbow.rbi +269 -0
  76. data/sorbet/rbi/annotations/webmock.rbi +9 -0
  77. data/sorbet/rbi/dsl/.gitattributes +1 -0
  78. data/sorbet/rbi/dsl/active_support/callbacks.rbi +22 -0
  79. data/sorbet/rbi/gems/.gitattributes +1 -0
  80. data/sorbet/rbi/gems/activesupport@7.1.3.rbi +18004 -0
  81. data/sorbet/rbi/gems/addressable@2.8.6.rbi +1993 -0
  82. data/sorbet/rbi/gems/appraisal@2.5.0.rbi +621 -0
  83. data/sorbet/rbi/gems/ast@2.4.2.rbi +584 -0
  84. data/sorbet/rbi/gems/base64@0.2.0.rbi +508 -0
  85. data/sorbet/rbi/gems/bigdecimal@3.1.6.rbi +77 -0
  86. data/sorbet/rbi/gems/coderay@1.1.3.rbi +3426 -0
  87. data/sorbet/rbi/gems/concurrent-ruby@1.2.3.rbi +11590 -0
  88. data/sorbet/rbi/gems/connection_pool@2.4.1.rbi +8 -0
  89. data/sorbet/rbi/gems/crack@0.4.5.rbi +144 -0
  90. data/sorbet/rbi/gems/dotenv@2.8.1.rbi +234 -0
  91. data/sorbet/rbi/gems/drb@2.2.0.rbi +1346 -0
  92. data/sorbet/rbi/gems/erubi@1.12.0.rbi +145 -0
  93. data/sorbet/rbi/gems/faraday-net_http@3.1.0.rbi +146 -0
  94. data/sorbet/rbi/gems/faraday@2.9.0.rbi +2911 -0
  95. data/sorbet/rbi/gems/hashdiff@1.1.0.rbi +352 -0
  96. data/sorbet/rbi/gems/i18n@1.14.1.rbi +2325 -0
  97. data/sorbet/rbi/gems/json@2.7.1.rbi +1561 -0
  98. data/sorbet/rbi/gems/language_server-protocol@3.17.0.3.rbi +14237 -0
  99. data/sorbet/rbi/gems/method_source@1.0.0.rbi +272 -0
  100. data/sorbet/rbi/gems/minitest@5.21.2.rbi +2197 -0
  101. data/sorbet/rbi/gems/mocha@2.1.0.rbi +3934 -0
  102. data/sorbet/rbi/gems/mutex_m@0.2.0.rbi +93 -0
  103. data/sorbet/rbi/gems/net-http@0.4.1.rbi +4068 -0
  104. data/sorbet/rbi/gems/netrc@0.11.0.rbi +158 -0
  105. data/sorbet/rbi/gems/parallel@1.24.0.rbi +280 -0
  106. data/sorbet/rbi/gems/parser@3.3.0.5.rbi +5472 -0
  107. data/sorbet/rbi/gems/prettier_print@1.2.1.rbi +951 -0
  108. data/sorbet/rbi/gems/prism@0.19.0.rbi +29883 -0
  109. data/sorbet/rbi/gems/pry-sorbet@0.2.1.rbi +966 -0
  110. data/sorbet/rbi/gems/pry@0.14.2.rbi +10077 -0
  111. data/sorbet/rbi/gems/public_suffix@5.0.4.rbi +935 -0
  112. data/sorbet/rbi/gems/racc@1.7.3.rbi +161 -0
  113. data/sorbet/rbi/gems/rack@3.0.8.rbi +5183 -0
  114. data/sorbet/rbi/gems/rainbow@3.1.1.rbi +402 -0
  115. data/sorbet/rbi/gems/rake@13.1.0.rbi +3027 -0
  116. data/sorbet/rbi/gems/rbi@0.1.6.rbi +2922 -0
  117. data/sorbet/rbi/gems/regexp_parser@2.9.0.rbi +3771 -0
  118. data/sorbet/rbi/gems/rexml@3.2.6.rbi +4781 -0
  119. data/sorbet/rbi/gems/rubocop-ast@1.30.0.rbi +7117 -0
  120. data/sorbet/rbi/gems/rubocop-minitest@0.34.5.rbi +2576 -0
  121. data/sorbet/rbi/gems/rubocop-rails@2.23.1.rbi +9175 -0
  122. data/sorbet/rbi/gems/rubocop-shopify@2.14.0.rbi +8 -0
  123. data/sorbet/rbi/gems/rubocop-sorbet@0.7.6.rbi +1510 -0
  124. data/sorbet/rbi/gems/rubocop@1.60.1.rbi +57356 -0
  125. data/sorbet/rbi/gems/ruby-progressbar@1.13.0.rbi +1317 -0
  126. data/sorbet/rbi/gems/ruby2_keywords@0.0.5.rbi +8 -0
  127. data/sorbet/rbi/gems/spoom@1.2.4.rbi +3777 -0
  128. data/sorbet/rbi/gems/syntax_tree@6.2.0.rbi +23136 -0
  129. data/sorbet/rbi/gems/tapioca@0.12.0.rbi +3506 -0
  130. data/sorbet/rbi/gems/thor@1.3.0.rbi +4312 -0
  131. data/sorbet/rbi/gems/toys-core@0.15.4.rbi +9462 -0
  132. data/sorbet/rbi/gems/toys@0.15.4.rbi +243 -0
  133. data/sorbet/rbi/gems/tzinfo@2.0.6.rbi +5917 -0
  134. data/sorbet/rbi/gems/unicode-display_width@2.5.0.rbi +65 -0
  135. data/sorbet/rbi/gems/uri@0.13.0.rbi +2327 -0
  136. data/sorbet/rbi/gems/vcr@6.2.0.rbi +3036 -0
  137. data/sorbet/rbi/gems/webmock@3.19.1.rbi +1768 -0
  138. data/sorbet/rbi/gems/yard-sorbet@0.8.1.rbi +428 -0
  139. data/sorbet/rbi/gems/yard@0.9.34.rbi +18084 -0
  140. data/sorbet/shims/module.rbi +6 -0
  141. data/sorbet/tapioca/require.rb +10 -0
  142. metadata +228 -0
@@ -0,0 +1,52 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module LunchMoney
5
+ # Slimmed down version of https://lunchmoney.dev/#transaction-object used as a base for other transaction objects
6
+ class TransactionBase < LunchMoney::DataObject
7
+ sig { returns(Integer) }
8
+ attr_accessor :id
9
+
10
+ sig { returns(Number) }
11
+ attr_accessor :to_base
12
+
13
+ sig { returns(T.nilable(Integer)) }
14
+ attr_accessor :asset_id, :plaid_account_id
15
+
16
+ sig { returns(String) }
17
+ attr_accessor :date,
18
+ :amount,
19
+ :currency,
20
+ :payee
21
+
22
+ sig { returns(T.nilable(String)) }
23
+ attr_accessor :notes
24
+
25
+ sig do
26
+ params(
27
+ id: Integer,
28
+ date: String,
29
+ amount: String,
30
+ currency: String,
31
+ to_base: Number,
32
+ payee: String,
33
+ notes: T.nilable(String),
34
+ asset_id: T.nilable(Integer),
35
+ plaid_account_id: T.nilable(Integer),
36
+ ).void
37
+ end
38
+ def initialize(id:, date:, amount:, currency:, to_base:, payee:, notes: nil, asset_id: nil,
39
+ plaid_account_id: nil)
40
+ super()
41
+ @id = id
42
+ @date = date
43
+ @amount = amount
44
+ @currency = currency
45
+ @to_base = to_base
46
+ @payee = payee
47
+ @notes = notes
48
+ @asset_id = asset_id
49
+ @plaid_account_id = plaid_account_id
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,30 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module LunchMoney
5
+ # Base object used for transaction objects that are used to update
6
+ # transactions https://lunchmoney.dev/#update-transaction
7
+ class TransactionModificationBase < LunchMoney::DataObject
8
+ sig { returns(T.nilable(String)) }
9
+ attr_accessor :payee, :date, :notes
10
+
11
+ sig { returns(T.nilable(Integer)) }
12
+ attr_accessor :category_id
13
+
14
+ sig do
15
+ params(
16
+ payee: T.nilable(String),
17
+ date: T.nilable(String),
18
+ category_id: T.nilable(Integer),
19
+ notes: T.nilable(String),
20
+ ).void
21
+ end
22
+ def initialize(payee: nil, date: nil, category_id: nil, notes: nil)
23
+ super()
24
+ @payee = payee
25
+ @date = date
26
+ @category_id = category_id
27
+ @notes = notes
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,43 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module LunchMoney
5
+ # object used when updating a transaction https://lunchmoney.dev/#update-transaction
6
+ class UpdateTransaction < TransactionModificationBase
7
+ sig { returns(T.nilable(String)) }
8
+ attr_accessor :amount, :currency, :status, :external_id
9
+
10
+ sig { returns(T.nilable(Integer)) }
11
+ attr_accessor :asset_id, :recurring_id
12
+
13
+ sig { returns(T.nilable(T::Array[T.any(String, Integer)])) }
14
+ attr_accessor :tags
15
+
16
+ sig do
17
+ params(
18
+ tags: T.nilable(T::Array[T.any(String, Integer)]),
19
+ category_id: T.nilable(Integer),
20
+ payee: T.nilable(String),
21
+ amount: T.nilable(String),
22
+ currency: T.nilable(String),
23
+ asset_id: T.nilable(Integer),
24
+ recurring_id: T.nilable(Integer),
25
+ notes: T.nilable(String),
26
+ status: T.nilable(String),
27
+ external_id: T.nilable(String),
28
+ date: T.nilable(String),
29
+ ).void
30
+ end
31
+ def initialize(tags: nil, category_id: nil, payee: nil, amount: nil, currency: nil, asset_id: nil,
32
+ recurring_id: nil, notes: nil, status: nil, external_id: nil, date: nil)
33
+ super(payee:, date:, category_id:, notes:)
34
+ @amount = amount
35
+ @tags = tags
36
+ @currency = currency
37
+ @asset_id = asset_id
38
+ @recurring_id = recurring_id
39
+ @status = status
40
+ @external_id = external_id
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,218 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "transaction/transaction_base"
5
+ require_relative "transaction/child_transaction"
6
+ require_relative "transaction/transaction"
7
+ require_relative "transaction/transaction_modification_base"
8
+ require_relative "transaction/split"
9
+ require_relative "transaction/update_transaction"
10
+
11
+ module LunchMoney
12
+ # https://lunchmoney.dev/#transactions
13
+ class TransactionCalls < ApiCall
14
+ sig do
15
+ params(
16
+ tag_id: T.nilable(Integer),
17
+ recurring_id: T.nilable(Integer),
18
+ plaid_account_id: T.nilable(Integer),
19
+ category_id: T.nilable(Integer),
20
+ asset_id: T.nilable(Integer),
21
+ is_group: T.nilable(T::Boolean),
22
+ status: T.nilable(String),
23
+ start_date: T.nilable(String),
24
+ end_date: T.nilable(String),
25
+ debit_as_negative: T.nilable(T::Boolean),
26
+ pending: T.nilable(T::Boolean),
27
+ offset: T.nilable(Integer),
28
+ limit: T.nilable(Integer),
29
+ ).returns(T.any(T::Array[LunchMoney::Transaction], LunchMoney::Errors))
30
+ end
31
+ def transactions(
32
+ tag_id: nil,
33
+ recurring_id: nil,
34
+ plaid_account_id: nil,
35
+ category_id: nil,
36
+ asset_id: nil,
37
+ is_group: nil,
38
+ status: nil,
39
+ start_date: nil,
40
+ end_date: nil,
41
+ debit_as_negative: nil,
42
+ pending: nil,
43
+ offset: nil,
44
+ limit: nil
45
+ )
46
+
47
+ params = clean_params({
48
+ tag_id:,
49
+ recurring_id:,
50
+ plaid_account_id:,
51
+ category_id:,
52
+ asset_id:,
53
+ is_group:,
54
+ status:,
55
+ start_date:,
56
+ end_date:,
57
+ debit_as_negative:,
58
+ pending:,
59
+ offset:,
60
+ limit:,
61
+ })
62
+ response = get("transactions", query_params: params)
63
+
64
+ api_errors = errors(response)
65
+ return api_errors if api_errors.present?
66
+
67
+ response.body[:transactions].map do |transaction|
68
+ transaction[:tags].map! { |tag| LunchMoney::TagBase.new(**tag) }
69
+
70
+ transaction[:children]&.map! { |child_transaction| LunchMoney::ChildTransaction.new(**child_transaction) }
71
+
72
+ LunchMoney::Transaction.new(**transaction)
73
+ end
74
+ end
75
+
76
+ sig do
77
+ params(
78
+ transaction_id: Integer,
79
+ debit_as_negative: T.nilable(T::Boolean),
80
+ ).returns(T.any(LunchMoney::Transaction, LunchMoney::Errors))
81
+ end
82
+ def transaction(transaction_id, debit_as_negative: nil)
83
+ params = clean_params({ debit_as_negative: })
84
+ response = get("transactions/#{transaction_id}", query_params: params)
85
+
86
+ api_errors = errors(response)
87
+ return api_errors if api_errors.present?
88
+
89
+ LunchMoney::Transaction.new(**response.body)
90
+ end
91
+
92
+ sig do
93
+ params(
94
+ transactions: T::Array[LunchMoney::UpdateTransaction],
95
+ apply_rules: T.nilable(T::Boolean),
96
+ skip_duplicates: T.nilable(T::Boolean),
97
+ check_for_recurring: T.nilable(T::Boolean),
98
+ debit_as_negative: T.nilable(T::Boolean),
99
+ skip_balance_update: T.nilable(T::Boolean),
100
+ ).returns(T.any(T::Hash[Symbol, T::Array[Integer]], LunchMoney::Errors))
101
+ end
102
+ def insert_transactions(transactions, apply_rules: nil, skip_duplicates: nil,
103
+ check_for_recurring: nil, debit_as_negative: nil, skip_balance_update: nil)
104
+ params = clean_params({
105
+ transactions: transactions.map(&:serialize),
106
+ apply_rules:,
107
+ skip_duplicates:,
108
+ check_for_recurring:,
109
+ debit_as_negative:,
110
+ skip_balance_update:,
111
+ })
112
+ response = post("transactions", params)
113
+
114
+ api_errors = errors(response)
115
+ return api_errors if api_errors.present?
116
+
117
+ response.body
118
+ end
119
+
120
+ sig do
121
+ params(
122
+ transaction_id: Integer,
123
+ transaction: T.nilable(LunchMoney::UpdateTransaction),
124
+ split: T.nilable(T::Array[LunchMoney::Split]),
125
+ debit_as_negative: T.nilable(T::Boolean),
126
+ skip_balance_update: T.nilable(T::Boolean),
127
+ ).returns(T.any({ updated: T::Boolean, split: T.nilable(T::Array[Integer]) }, LunchMoney::Errors))
128
+ end
129
+ def update_transaction(transaction_id, transaction: nil, split: nil,
130
+ debit_as_negative: nil, skip_balance_update: nil)
131
+ raise(
132
+ LunchMoney::MissingArgument,
133
+ "Either a transaction or split must be provided",
134
+ ) if transaction.nil? && split.nil?
135
+
136
+ params = clean_params({
137
+ transaction: transaction&.serialize,
138
+ split: split&.map!(&:serialize),
139
+ debit_as_negative:,
140
+ skip_balance_update:,
141
+ })
142
+ response = put("transactions/#{transaction_id}", params)
143
+
144
+ api_errors = errors(response)
145
+ return api_errors if api_errors.present?
146
+
147
+ response.body
148
+ end
149
+
150
+ sig do
151
+ params(
152
+ parent_ids: T::Array[Integer],
153
+ remove_parents: T.nilable(T::Boolean),
154
+ ).returns(T.any(T::Array[Integer], LunchMoney::Errors))
155
+ end
156
+ def unsplit_transaction(parent_ids, remove_parents: false)
157
+ params = { parent_ids:, remove_parents: }
158
+ response = post("transactions/unsplit", params)
159
+
160
+ api_errors = errors(response)
161
+ return api_errors if api_errors.present?
162
+
163
+ response.body
164
+ end
165
+
166
+ sig { params(transaction_id: Integer).returns(T.any(LunchMoney::Transaction, LunchMoney::Errors)) }
167
+ def transaction_group(transaction_id)
168
+ response = get("transactions/group", query_params: { transaction_id: })
169
+
170
+ api_errors = errors(response)
171
+ return api_errors if api_errors.present?
172
+
173
+ LunchMoney::Transaction.new(**response.body)
174
+ end
175
+
176
+ sig do
177
+ params(
178
+ date: String,
179
+ payee: String,
180
+ transactions: T::Array[T.any(Integer, String)],
181
+ category_id: T.nilable(Integer),
182
+ notes: T.nilable(String),
183
+ tags: T.nilable(T::Array[T.any(Integer, String)]),
184
+ ).returns(T.any(Integer, LunchMoney::Errors))
185
+ end
186
+ def create_transaction_group(date:, payee:, transactions:, category_id: nil, notes: nil, tags: nil)
187
+ params = clean_params({
188
+ date:,
189
+ payee:,
190
+ transactions:,
191
+ category_id:,
192
+ notes:,
193
+ tags:,
194
+ })
195
+ response = post("transactions/group", params)
196
+
197
+ api_errors = errors(response)
198
+ return api_errors if api_errors.present?
199
+
200
+ response.body
201
+ end
202
+
203
+ sig do
204
+ params(transaction_id: T.any(
205
+ String,
206
+ Integer,
207
+ )).returns(T.any(T::Hash[Symbol, T::Array[Integer]], LunchMoney::Errors))
208
+ end
209
+ def delete_transaction_group(transaction_id)
210
+ response = delete("transactions/group/#{transaction_id}")
211
+
212
+ api_errors = errors(response)
213
+ return api_errors if api_errors.present?
214
+
215
+ response.body
216
+ end
217
+ end
218
+ end
@@ -0,0 +1,36 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module LunchMoney
5
+ # https://lunchmoney.dev/#user-object
6
+ class User < LunchMoney::DataObject
7
+ sig { returns(Integer) }
8
+ attr_accessor :user_id, :account_id
9
+
10
+ sig { returns(String) }
11
+ attr_accessor :user_name, :user_email, :budget_name
12
+
13
+ sig { returns(T.nilable(String)) }
14
+ attr_accessor :api_key_label
15
+
16
+ sig do
17
+ params(
18
+ user_id: Integer,
19
+ user_name: String,
20
+ user_email: String,
21
+ account_id: Integer,
22
+ budget_name: String,
23
+ api_key_label: T.nilable(String),
24
+ ).void
25
+ end
26
+ def initialize(user_id:, user_name:, user_email:, account_id:, budget_name:, api_key_label: nil)
27
+ super()
28
+ @user_id = user_id
29
+ @user_name = user_name
30
+ @user_email = user_email
31
+ @account_id = account_id
32
+ @budget_name = budget_name
33
+ @api_key_label = api_key_label
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,19 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "user"
5
+
6
+ module LunchMoney
7
+ # https://lunchmoney.dev/#user
8
+ class UserCalls < ApiCall
9
+ sig { returns(T.any(LunchMoney::User, LunchMoney::Errors)) }
10
+ def me
11
+ response = get("me")
12
+
13
+ api_errors = errors(response)
14
+ return api_errors if api_errors.present?
15
+
16
+ LunchMoney::User.new(**response.body)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,43 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "time"
5
+
6
+ module LunchMoney
7
+ # module containing reusable methods for validating data objects
8
+ module Validators
9
+ include Kernel
10
+
11
+ sig { params(value: String, valid_values: T::Array[String]).returns(String) }
12
+ def validate_one_of!(value, valid_values)
13
+ return value unless LunchMoney.validate_object_attributes?
14
+
15
+ if valid_values.exclude?(value)
16
+ raise(InvalidObjectAttribute, "#{value} is invalid, must be one of #{valid_values.join(", ")}")
17
+ end
18
+
19
+ value
20
+ end
21
+
22
+ sig { params(value: String).returns(String) }
23
+ def validate_iso8601!(value)
24
+ return value unless LunchMoney.validate_object_attributes?
25
+
26
+ raise(InvalidObjectAttribute, "#{value} is not a valid ISO 8601 string") unless valid_iso8601_string?(value)
27
+
28
+ value
29
+ end
30
+
31
+ private
32
+
33
+ sig { params(time_string: String).returns(T::Boolean) }
34
+ def valid_iso8601_string?(time_string)
35
+ Time.iso8601(time_string)
36
+ true
37
+ rescue ArgumentError => error
38
+ raise unless error.message.match?("invalid xmlschema format")
39
+
40
+ false
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,7 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module LunchMoney
5
+ # Current version of the gem
6
+ VERSION = "0.10.0"
7
+ end
data/lib/lunchmoney.rb ADDED
@@ -0,0 +1,54 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "active_support"
5
+ require "active_support/core_ext/module/delegation.rb"
6
+ require "active_support/core_ext/object/blank.rb"
7
+ require "faraday"
8
+ require "sorbet-runtime"
9
+
10
+ # Include T::Sig directly in the module class so that it doesn't need to be extended everywhere.
11
+ class Module
12
+ include T::Sig
13
+ end
14
+
15
+ # Add type alias for when Integer and Float can both be used
16
+ Number = T.type_alias { T.any(Integer, Float) }
17
+
18
+ require_relative "lunchmoney/version"
19
+ require_relative "lunchmoney/validators"
20
+ require_relative "lunchmoney/api"
21
+
22
+ module LunchMoney
23
+ # Lock used to avoid config conflicts
24
+ LOCK = T.let(Mutex.new, Mutex)
25
+
26
+ class << self
27
+ # @example Set your API key
28
+ # LunchMoney.configure do |config|
29
+ # config.api_key = "your_api_key"
30
+ # end
31
+ #
32
+ # @example Turn off object validation
33
+ # LunchMoney.configure do |config|
34
+ # config.validate_object_attributes = false
35
+ # end
36
+ sig do
37
+ params(block: T.proc.params(arg0: LunchMoney::Configuration).void).void
38
+ end
39
+ def configure(&block)
40
+ yield(configuration)
41
+ end
42
+
43
+ sig { returns(LunchMoney::Configuration) }
44
+ def configuration
45
+ @configuration = T.let(nil, T.nilable(LunchMoney::Configuration)) unless defined?(@configuration)
46
+ @configuration || LOCK.synchronize { @configuration = LunchMoney::Configuration.new }
47
+ end
48
+
49
+ sig { returns(T::Boolean) }
50
+ def validate_object_attributes?
51
+ configuration.validate_object_attributes
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/lunchmoney/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "lunchmoney"
7
+ spec.version = LunchMoney::VERSION
8
+ spec.author = "@halorrr"
9
+ spec.email = "halorrr@gmail.com"
10
+
11
+ spec.summary = "LunchMoney API client library."
12
+ spec.homepage = "https://github.com/halorrr/lunchmoney"
13
+ spec.required_ruby_version = ">= 3.1"
14
+ spec.license = "MIT"
15
+
16
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = spec.homepage
19
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/releases"
20
+
21
+ # Specify which files should be added to the gem when it is released.
22
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
24
+ %x(git ls-files -z).split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
25
+ end
26
+
27
+ spec.bindir = "exe"
28
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
29
+ spec.require_paths = ["lib"]
30
+
31
+ spec.add_dependency("activesupport", ">= 6.1")
32
+ spec.add_dependency("faraday", ">= 1.0.0")
33
+ spec.add_dependency("sorbet-runtime", ">= 0.5")
34
+ end
data/sorbet/config ADDED
@@ -0,0 +1,5 @@
1
+ --dir
2
+ .
3
+ --ignore=/vendor
4
+ --ignore=/.toys
5
+ --enable-experimental-requires-ancestor
@@ -0,0 +1 @@
1
+ **/*.rbi linguist-vendored=true