lunchmoney 0.10.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 (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