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.
- checksums.yaml +7 -0
- data/.DS_Store +0 -0
- data/.github/dependabot.yml +18 -0
- data/.github/workflows/build_and_publish_yard_docs.yml +47 -0
- data/.github/workflows/ci.yml +58 -0
- data/.github/workflows/dependabot-rbi-updater.yml +43 -0
- data/.github/workflows/publish_gem.yml +31 -0
- data/.gitignore +62 -0
- data/.rubocop.yml +45 -0
- data/.ruby-version +1 -0
- data/.toys/.toys.rb +10 -0
- data/.toys/ci.rb +22 -0
- data/.toys/rbi.rb +60 -0
- data/.toys/rubocop.rb +10 -0
- data/.toys/spoom.rb +15 -0
- data/.toys/typecheck.rb +5 -0
- data/.yardopts +2 -0
- data/Appraisals +22 -0
- data/Gemfile +25 -0
- data/Gemfile.lock +174 -0
- data/LICENSE +21 -0
- data/README.md +57 -0
- data/bin/console +16 -0
- data/bin/rubocop +27 -0
- data/bin/setup +8 -0
- data/bin/spoom +27 -0
- data/bin/srb +27 -0
- data/bin/tapioca +27 -0
- data/bin/toys +27 -0
- data/bin/yard +27 -0
- data/lib/lunchmoney/api.rb +147 -0
- data/lib/lunchmoney/api_call.rb +109 -0
- data/lib/lunchmoney/assets/asset.rb +89 -0
- data/lib/lunchmoney/assets/asset_calls.rb +96 -0
- data/lib/lunchmoney/budget/budget.rb +74 -0
- data/lib/lunchmoney/budget/budget_calls.rb +82 -0
- data/lib/lunchmoney/budget/config.rb +38 -0
- data/lib/lunchmoney/budget/data.rb +42 -0
- data/lib/lunchmoney/categories/category/category.rb +52 -0
- data/lib/lunchmoney/categories/category/child_category.rb +42 -0
- data/lib/lunchmoney/categories/category_calls.rb +195 -0
- data/lib/lunchmoney/configuration.rb +26 -0
- data/lib/lunchmoney/crypto/crypto/crypto.rb +43 -0
- data/lib/lunchmoney/crypto/crypto/crypto_base.rb +65 -0
- data/lib/lunchmoney/crypto/crypto_calls.rb +49 -0
- data/lib/lunchmoney/data_object.rb +25 -0
- data/lib/lunchmoney/errors.rb +19 -0
- data/lib/lunchmoney/exceptions.rb +19 -0
- data/lib/lunchmoney/plaid_accounts/plaid_account.rb +73 -0
- data/lib/lunchmoney/plaid_accounts/plaid_account_calls.rb +38 -0
- data/lib/lunchmoney/recurring_expenses/recurring_expense/recurring_expense.rb +65 -0
- data/lib/lunchmoney/recurring_expenses/recurring_expense/recurring_expense_base.rb +29 -0
- data/lib/lunchmoney/recurring_expenses/recurring_expense_calls.rb +28 -0
- data/lib/lunchmoney/tags/tag/tag.rb +20 -0
- data/lib/lunchmoney/tags/tag/tag_base.rb +21 -0
- data/lib/lunchmoney/tags/tag_calls.rb +20 -0
- data/lib/lunchmoney/transactions/transaction/child_transaction.rb +31 -0
- data/lib/lunchmoney/transactions/transaction/split.rb +24 -0
- data/lib/lunchmoney/transactions/transaction/transaction.rb +156 -0
- data/lib/lunchmoney/transactions/transaction/transaction_base.rb +52 -0
- data/lib/lunchmoney/transactions/transaction/transaction_modification_base.rb +30 -0
- data/lib/lunchmoney/transactions/transaction/update_transaction.rb +43 -0
- data/lib/lunchmoney/transactions/transaction_calls.rb +218 -0
- data/lib/lunchmoney/user/user.rb +36 -0
- data/lib/lunchmoney/user/user_calls.rb +19 -0
- data/lib/lunchmoney/validators.rb +43 -0
- data/lib/lunchmoney/version.rb +7 -0
- data/lib/lunchmoney.rb +54 -0
- data/lunchmoney.gemspec +34 -0
- data/sorbet/config +5 -0
- data/sorbet/rbi/annotations/.gitattributes +1 -0
- data/sorbet/rbi/annotations/activesupport.rbi +410 -0
- data/sorbet/rbi/annotations/faraday.rbi +17 -0
- data/sorbet/rbi/annotations/mocha.rbi +34 -0
- data/sorbet/rbi/annotations/rainbow.rbi +269 -0
- data/sorbet/rbi/annotations/webmock.rbi +9 -0
- data/sorbet/rbi/dsl/.gitattributes +1 -0
- data/sorbet/rbi/dsl/active_support/callbacks.rbi +22 -0
- data/sorbet/rbi/gems/.gitattributes +1 -0
- data/sorbet/rbi/gems/activesupport@7.1.3.rbi +18004 -0
- data/sorbet/rbi/gems/addressable@2.8.6.rbi +1993 -0
- data/sorbet/rbi/gems/appraisal@2.5.0.rbi +621 -0
- data/sorbet/rbi/gems/ast@2.4.2.rbi +584 -0
- data/sorbet/rbi/gems/base64@0.2.0.rbi +508 -0
- data/sorbet/rbi/gems/bigdecimal@3.1.6.rbi +77 -0
- data/sorbet/rbi/gems/coderay@1.1.3.rbi +3426 -0
- data/sorbet/rbi/gems/concurrent-ruby@1.2.3.rbi +11590 -0
- data/sorbet/rbi/gems/connection_pool@2.4.1.rbi +8 -0
- data/sorbet/rbi/gems/crack@0.4.5.rbi +144 -0
- data/sorbet/rbi/gems/dotenv@2.8.1.rbi +234 -0
- data/sorbet/rbi/gems/drb@2.2.0.rbi +1346 -0
- data/sorbet/rbi/gems/erubi@1.12.0.rbi +145 -0
- data/sorbet/rbi/gems/faraday-net_http@3.1.0.rbi +146 -0
- data/sorbet/rbi/gems/faraday@2.9.0.rbi +2911 -0
- data/sorbet/rbi/gems/hashdiff@1.1.0.rbi +352 -0
- data/sorbet/rbi/gems/i18n@1.14.1.rbi +2325 -0
- data/sorbet/rbi/gems/json@2.7.1.rbi +1561 -0
- data/sorbet/rbi/gems/language_server-protocol@3.17.0.3.rbi +14237 -0
- data/sorbet/rbi/gems/method_source@1.0.0.rbi +272 -0
- data/sorbet/rbi/gems/minitest@5.21.2.rbi +2197 -0
- data/sorbet/rbi/gems/mocha@2.1.0.rbi +3934 -0
- data/sorbet/rbi/gems/mutex_m@0.2.0.rbi +93 -0
- data/sorbet/rbi/gems/net-http@0.4.1.rbi +4068 -0
- data/sorbet/rbi/gems/netrc@0.11.0.rbi +158 -0
- data/sorbet/rbi/gems/parallel@1.24.0.rbi +280 -0
- data/sorbet/rbi/gems/parser@3.3.0.5.rbi +5472 -0
- data/sorbet/rbi/gems/prettier_print@1.2.1.rbi +951 -0
- data/sorbet/rbi/gems/prism@0.19.0.rbi +29883 -0
- data/sorbet/rbi/gems/pry-sorbet@0.2.1.rbi +966 -0
- data/sorbet/rbi/gems/pry@0.14.2.rbi +10077 -0
- data/sorbet/rbi/gems/public_suffix@5.0.4.rbi +935 -0
- data/sorbet/rbi/gems/racc@1.7.3.rbi +161 -0
- data/sorbet/rbi/gems/rack@3.0.8.rbi +5183 -0
- data/sorbet/rbi/gems/rainbow@3.1.1.rbi +402 -0
- data/sorbet/rbi/gems/rake@13.1.0.rbi +3027 -0
- data/sorbet/rbi/gems/rbi@0.1.6.rbi +2922 -0
- data/sorbet/rbi/gems/regexp_parser@2.9.0.rbi +3771 -0
- data/sorbet/rbi/gems/rexml@3.2.6.rbi +4781 -0
- data/sorbet/rbi/gems/rubocop-ast@1.30.0.rbi +7117 -0
- data/sorbet/rbi/gems/rubocop-minitest@0.34.5.rbi +2576 -0
- data/sorbet/rbi/gems/rubocop-rails@2.23.1.rbi +9175 -0
- data/sorbet/rbi/gems/rubocop-shopify@2.14.0.rbi +8 -0
- data/sorbet/rbi/gems/rubocop-sorbet@0.7.6.rbi +1510 -0
- data/sorbet/rbi/gems/rubocop@1.60.1.rbi +57356 -0
- data/sorbet/rbi/gems/ruby-progressbar@1.13.0.rbi +1317 -0
- data/sorbet/rbi/gems/ruby2_keywords@0.0.5.rbi +8 -0
- data/sorbet/rbi/gems/spoom@1.2.4.rbi +3777 -0
- data/sorbet/rbi/gems/syntax_tree@6.2.0.rbi +23136 -0
- data/sorbet/rbi/gems/tapioca@0.12.0.rbi +3506 -0
- data/sorbet/rbi/gems/thor@1.3.0.rbi +4312 -0
- data/sorbet/rbi/gems/toys-core@0.15.4.rbi +9462 -0
- data/sorbet/rbi/gems/toys@0.15.4.rbi +243 -0
- data/sorbet/rbi/gems/tzinfo@2.0.6.rbi +5917 -0
- data/sorbet/rbi/gems/unicode-display_width@2.5.0.rbi +65 -0
- data/sorbet/rbi/gems/uri@0.13.0.rbi +2327 -0
- data/sorbet/rbi/gems/vcr@6.2.0.rbi +3036 -0
- data/sorbet/rbi/gems/webmock@3.19.1.rbi +1768 -0
- data/sorbet/rbi/gems/yard-sorbet@0.8.1.rbi +428 -0
- data/sorbet/rbi/gems/yard@0.9.34.rbi +18084 -0
- data/sorbet/shims/module.rbi +6 -0
- data/sorbet/tapioca/require.rb +10 -0
- metadata +228 -0
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2021 halorrr
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# lunchmoney
|
2
|
+
|
3
|
+
This gem and readme are very much a work in progress. More to come!
|
4
|
+
|
5
|
+
This gem is a library of the [LunchMoney API](https://lunchmoney.dev/) for the wonderful [LunchMoney](http://lunchmoney.app/) web app for personal finance & budgeting.
|
6
|
+
|
7
|
+
You can find the yard docs for this gem [here](https://halorrr.github.io/lunchmoney/)
|
8
|
+
|
9
|
+
## Usage
|
10
|
+
|
11
|
+
### Installation
|
12
|
+
|
13
|
+
Add this line to your application's `Gemfile`:
|
14
|
+
|
15
|
+
```Ruby
|
16
|
+
gem "lunchmoney"
|
17
|
+
```
|
18
|
+
|
19
|
+
### Set your lunchmoney token
|
20
|
+
|
21
|
+
There are a few ways you can set your API token. You can set it manually using a configure block:
|
22
|
+
|
23
|
+
```Ruby
|
24
|
+
LunchMoney.configure do |config|
|
25
|
+
config.api_key = "your_api_key"
|
26
|
+
end
|
27
|
+
```
|
28
|
+
|
29
|
+
The config will also _automatically_ pull in the token if set via environment variable named `LUNCHMONEY_TOKEN`
|
30
|
+
|
31
|
+
You can also override the config and set your LunchMoney token for a specific API instance via kwarg:
|
32
|
+
|
33
|
+
```Ruby
|
34
|
+
LunchMoney::Api.new(api_key: "your_api_key")
|
35
|
+
```
|
36
|
+
|
37
|
+
### Using the API
|
38
|
+
|
39
|
+
Create an instance of the api, then call the endpoint you need:
|
40
|
+
|
41
|
+
```Ruby
|
42
|
+
api = LunchMoney::Api.new
|
43
|
+
api.categories
|
44
|
+
```
|
45
|
+
|
46
|
+
## Contributing to this repo
|
47
|
+
|
48
|
+
Feel free to contribute and submit PRs to improve this gem
|
49
|
+
|
50
|
+
## Releasing a new gem version
|
51
|
+
|
52
|
+
1. Bump the `VERSION` constant in `lib/lunchmoney/version.rb`
|
53
|
+
2. Run `bundle install`
|
54
|
+
3. Commit and push up the change in a PR
|
55
|
+
4. Merge the PR
|
56
|
+
5. Create a new tag and release with the name version as v0.0.0
|
57
|
+
6. A Github action will kick off and publish the new gem version
|
data/bin/console
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "dotenv/load"
|
6
|
+
require "lunchmoney"
|
7
|
+
|
8
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
9
|
+
# with your gem easier. You can also use a different console, if you like.
|
10
|
+
|
11
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
12
|
+
# require "pry"
|
13
|
+
# Pry.start
|
14
|
+
|
15
|
+
require "irb"
|
16
|
+
IRB.start(__FILE__)
|
data/bin/rubocop
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rubocop' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
|
12
|
+
|
13
|
+
bundle_binstub = File.expand_path("bundle", __dir__)
|
14
|
+
|
15
|
+
if File.file?(bundle_binstub)
|
16
|
+
if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
|
17
|
+
load(bundle_binstub)
|
18
|
+
else
|
19
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
20
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
require "rubygems"
|
25
|
+
require "bundler/setup"
|
26
|
+
|
27
|
+
load Gem.bin_path("rubocop", "rubocop")
|
data/bin/setup
ADDED
data/bin/spoom
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'spoom' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
|
12
|
+
|
13
|
+
bundle_binstub = File.expand_path("bundle", __dir__)
|
14
|
+
|
15
|
+
if File.file?(bundle_binstub)
|
16
|
+
if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
|
17
|
+
load(bundle_binstub)
|
18
|
+
else
|
19
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
20
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
require "rubygems"
|
25
|
+
require "bundler/setup"
|
26
|
+
|
27
|
+
load Gem.bin_path("spoom", "spoom")
|
data/bin/srb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'srb' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
|
12
|
+
|
13
|
+
bundle_binstub = File.expand_path("bundle", __dir__)
|
14
|
+
|
15
|
+
if File.file?(bundle_binstub)
|
16
|
+
if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
|
17
|
+
load(bundle_binstub)
|
18
|
+
else
|
19
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
20
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
require "rubygems"
|
25
|
+
require "bundler/setup"
|
26
|
+
|
27
|
+
load Gem.bin_path("sorbet", "srb")
|
data/bin/tapioca
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'tapioca' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
|
12
|
+
|
13
|
+
bundle_binstub = File.expand_path("bundle", __dir__)
|
14
|
+
|
15
|
+
if File.file?(bundle_binstub)
|
16
|
+
if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
|
17
|
+
load(bundle_binstub)
|
18
|
+
else
|
19
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
20
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
require "rubygems"
|
25
|
+
require "bundler/setup"
|
26
|
+
|
27
|
+
load Gem.bin_path("tapioca", "tapioca")
|
data/bin/toys
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'toys' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
|
12
|
+
|
13
|
+
bundle_binstub = File.expand_path("bundle", __dir__)
|
14
|
+
|
15
|
+
if File.file?(bundle_binstub)
|
16
|
+
if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
|
17
|
+
load(bundle_binstub)
|
18
|
+
else
|
19
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
20
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
require "rubygems"
|
25
|
+
require "bundler/setup"
|
26
|
+
|
27
|
+
load Gem.bin_path("toys", "toys")
|
data/bin/yard
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'yard' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
|
12
|
+
|
13
|
+
bundle_binstub = File.expand_path("bundle", __dir__)
|
14
|
+
|
15
|
+
if File.file?(bundle_binstub)
|
16
|
+
if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
|
17
|
+
load(bundle_binstub)
|
18
|
+
else
|
19
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
20
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
require "rubygems"
|
25
|
+
require "bundler/setup"
|
26
|
+
|
27
|
+
load Gem.bin_path("yard", "yard")
|
@@ -0,0 +1,147 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative "exceptions"
|
5
|
+
require_relative "configuration"
|
6
|
+
|
7
|
+
require_relative "api_call"
|
8
|
+
require_relative "data_object"
|
9
|
+
require_relative "user/user_calls"
|
10
|
+
require_relative "categories/category_calls"
|
11
|
+
require_relative "tags/tag_calls"
|
12
|
+
require_relative "transactions/transaction_calls"
|
13
|
+
require_relative "recurring_expenses/recurring_expense_calls"
|
14
|
+
require_relative "budget/budget_calls"
|
15
|
+
require_relative "assets/asset_calls"
|
16
|
+
require_relative "plaid_accounts/plaid_account_calls"
|
17
|
+
require_relative "crypto/crypto_calls"
|
18
|
+
|
19
|
+
module LunchMoney
|
20
|
+
# The main API class that a user should interface through the method of any individual call is delegated through here
|
21
|
+
# so that it is never necessary to go through things like `LunchMoney::UserCalls.new.user` instead you can directly
|
22
|
+
# call the endpoint with LunchMoney::Api.new.user and it will be delegated to the correct call.
|
23
|
+
class Api
|
24
|
+
sig { returns(T.nilable(String)) }
|
25
|
+
attr_reader :api_key
|
26
|
+
|
27
|
+
sig { params(api_key: T.nilable(String)).void }
|
28
|
+
def initialize(api_key: nil)
|
29
|
+
@api_key = T.let((api_key || LunchMoney.configuration.api_key), T.nilable(String))
|
30
|
+
end
|
31
|
+
|
32
|
+
delegate :me, to: :user_calls
|
33
|
+
|
34
|
+
sig { returns(LunchMoney::ApiCall) }
|
35
|
+
def user_calls
|
36
|
+
with_valid_api_key do
|
37
|
+
@user_calls ||= T.let(LunchMoney::UserCalls.new(api_key:), T.nilable(LunchMoney::UserCalls))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
delegate :categories,
|
42
|
+
:category,
|
43
|
+
:create_category,
|
44
|
+
:create_category_group,
|
45
|
+
:update_category,
|
46
|
+
:add_to_category_group,
|
47
|
+
:delete_category,
|
48
|
+
:force_delete_category,
|
49
|
+
to: :category_calls
|
50
|
+
|
51
|
+
sig { returns(LunchMoney::ApiCall) }
|
52
|
+
def category_calls
|
53
|
+
with_valid_api_key do
|
54
|
+
@category_calls ||= T.let(LunchMoney::CategoryCalls.new(api_key:), T.nilable(LunchMoney::CategoryCalls))
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
delegate :tags, to: :tag_calls
|
59
|
+
|
60
|
+
sig { returns(LunchMoney::ApiCall) }
|
61
|
+
def tag_calls
|
62
|
+
with_valid_api_key do
|
63
|
+
@tag_calls ||= T.let(LunchMoney::TagCalls.new(api_key:), T.nilable(LunchMoney::TagCalls))
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
delegate :transactions,
|
68
|
+
:transaction,
|
69
|
+
:insert_transactions,
|
70
|
+
:update_transaction,
|
71
|
+
:unsplit_transaction,
|
72
|
+
:transaction_group,
|
73
|
+
:create_transaction_group,
|
74
|
+
:delete_transaction_group,
|
75
|
+
to: :transaction_calls
|
76
|
+
|
77
|
+
sig { returns(LunchMoney::ApiCall) }
|
78
|
+
def transaction_calls
|
79
|
+
with_valid_api_key do
|
80
|
+
@transaction_calls ||= T.let(
|
81
|
+
LunchMoney::TransactionCalls.new(api_key:),
|
82
|
+
T.nilable(LunchMoney::TransactionCalls),
|
83
|
+
)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
delegate :recurring_expenses, to: :recurring_expense_calls
|
88
|
+
|
89
|
+
sig { returns(LunchMoney::ApiCall) }
|
90
|
+
def recurring_expense_calls
|
91
|
+
with_valid_api_key do
|
92
|
+
@recurring_expense_calls ||= T.let(
|
93
|
+
LunchMoney::RecurringExpenseCalls.new(api_key:),
|
94
|
+
T.nilable(LunchMoney::RecurringExpenseCalls),
|
95
|
+
)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
delegate :budgets, :upsert_budget, :remove_budget, to: :budget_calls
|
100
|
+
|
101
|
+
sig { returns(LunchMoney::ApiCall) }
|
102
|
+
def budget_calls
|
103
|
+
with_valid_api_key do
|
104
|
+
@budget_calls ||= T.let(LunchMoney::BudgetCalls.new(api_key:), T.nilable(LunchMoney::BudgetCalls))
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
delegate :assets, :create_asset, :update_asset, to: :asset_calls
|
109
|
+
|
110
|
+
sig { returns(LunchMoney::ApiCall) }
|
111
|
+
def asset_calls
|
112
|
+
with_valid_api_key do
|
113
|
+
@asset_calls ||= T.let(LunchMoney::AssetCalls.new(api_key:), T.nilable(LunchMoney::AssetCalls))
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
delegate :plaid_accounts, :plaid_accounts_fetch, to: :plaid_account_calls
|
118
|
+
|
119
|
+
sig { returns(LunchMoney::ApiCall) }
|
120
|
+
def plaid_account_calls
|
121
|
+
with_valid_api_key do
|
122
|
+
@plaid_account_calls ||= T.let(
|
123
|
+
LunchMoney::PlaidAccountCalls.new(api_key:),
|
124
|
+
T.nilable(LunchMoney::PlaidAccountCalls),
|
125
|
+
)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
delegate :crypto, :update_crypto, to: :crypto_calls
|
130
|
+
|
131
|
+
sig { returns(LunchMoney::ApiCall) }
|
132
|
+
def crypto_calls
|
133
|
+
with_valid_api_key do
|
134
|
+
@crypto_calls ||= T.let(LunchMoney::CryptoCalls.new(api_key:), T.nilable(LunchMoney::CryptoCalls))
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
private
|
139
|
+
|
140
|
+
sig { params(block: T.proc.returns(LunchMoney::ApiCall)).returns(LunchMoney::ApiCall) }
|
141
|
+
def with_valid_api_key(&block)
|
142
|
+
raise(InvalidApiKey, "API key is missing or invalid") if api_key.blank?
|
143
|
+
|
144
|
+
yield
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative "errors"
|
5
|
+
|
6
|
+
module LunchMoney
|
7
|
+
# Base class for all API call types
|
8
|
+
class ApiCall
|
9
|
+
# Base URL used for API calls
|
10
|
+
BASE_URL = "https://dev.lunchmoney.app/v1/"
|
11
|
+
|
12
|
+
sig { returns(T.nilable(String)) }
|
13
|
+
attr_reader :api_key
|
14
|
+
|
15
|
+
sig { params(api_key: T.nilable(String)).void }
|
16
|
+
def initialize(api_key: nil)
|
17
|
+
@api_key = T.let((api_key || LunchMoney.configuration.api_key), T.nilable(String))
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
sig { params(endpoint: String, query_params: T.nilable(T::Hash[Symbol, T.untyped])).returns(Faraday::Response) }
|
23
|
+
def get(endpoint, query_params: nil)
|
24
|
+
connection = request(flat_params: true)
|
25
|
+
|
26
|
+
if query_params.present?
|
27
|
+
connection.get(BASE_URL + endpoint, query_params)
|
28
|
+
else
|
29
|
+
connection.get(BASE_URL + endpoint)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
sig { params(endpoint: String, params: T.nilable(T::Hash[Symbol, T.untyped])).returns(Faraday::Response) }
|
34
|
+
def post(endpoint, params)
|
35
|
+
request(json_request: true).post(BASE_URL + endpoint, params)
|
36
|
+
end
|
37
|
+
|
38
|
+
sig { params(endpoint: String, body: T::Hash[Symbol, T.untyped]).returns(Faraday::Response) }
|
39
|
+
def put(endpoint, body)
|
40
|
+
request(json_request: true).put(BASE_URL + endpoint) do |req|
|
41
|
+
req.body = body
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
sig { params(endpoint: String, query_params: T.nilable(T::Hash[Symbol, T.untyped])).returns(Faraday::Response) }
|
46
|
+
def delete(endpoint, query_params: nil)
|
47
|
+
connection = request(flat_params: true)
|
48
|
+
|
49
|
+
if query_params.present?
|
50
|
+
connection.delete(BASE_URL + endpoint, query_params)
|
51
|
+
else
|
52
|
+
connection.delete(BASE_URL + endpoint)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
sig { params(json_request: T::Boolean, flat_params: T::Boolean).returns(Faraday::Connection) }
|
57
|
+
def request(json_request: false, flat_params: false)
|
58
|
+
Faraday.new do |conn|
|
59
|
+
conn.request(:authorization, "Bearer", @api_key)
|
60
|
+
conn.request(:json) if json_request
|
61
|
+
# conn.options.params_encoder = Faraday::FlatParamsEncoder if flat_params
|
62
|
+
conn.response(:json, content_type: /json$/, parser_options: { symbolize_names: true })
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
sig { params(response: Faraday::Response).returns(LunchMoney::Errors) }
|
67
|
+
def errors(response)
|
68
|
+
body = response.body
|
69
|
+
|
70
|
+
return parse_errors(body) unless error_hash(body).nil?
|
71
|
+
|
72
|
+
LunchMoney::Errors.new
|
73
|
+
end
|
74
|
+
|
75
|
+
sig { params(body: T::Hash[Symbol, T.any(String, T::Array[String])]).returns(LunchMoney::Errors) }
|
76
|
+
def parse_errors(body)
|
77
|
+
errors = error_hash(body)
|
78
|
+
api_errors = LunchMoney::Errors.new
|
79
|
+
return api_errors if errors.blank?
|
80
|
+
|
81
|
+
case errors
|
82
|
+
when String
|
83
|
+
api_errors << errors
|
84
|
+
when Array
|
85
|
+
errors.each { |error| api_errors << error }
|
86
|
+
end
|
87
|
+
|
88
|
+
api_errors
|
89
|
+
end
|
90
|
+
|
91
|
+
sig { params(body: T.untyped).returns(T.untyped) }
|
92
|
+
def error_hash(body)
|
93
|
+
return unless body.is_a?(Hash)
|
94
|
+
|
95
|
+
if body[:error]
|
96
|
+
body[:error]
|
97
|
+
elsif body[:errors]
|
98
|
+
body[:errors]
|
99
|
+
elsif body[:name] == "Error"
|
100
|
+
body[:message]
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
sig { params(params: T::Hash[Symbol, T.untyped]).returns(T::Hash[Symbol, T.untyped]) }
|
105
|
+
def clean_params(params)
|
106
|
+
params.reject! { |_key, value| value.nil? }
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module LunchMoney
|
5
|
+
# https://lunchmoney.dev/#assets-object
|
6
|
+
class Asset < LunchMoney::DataObject
|
7
|
+
include LunchMoney::Validators
|
8
|
+
|
9
|
+
sig { returns(Integer) }
|
10
|
+
attr_accessor :id
|
11
|
+
|
12
|
+
sig { returns(String) }
|
13
|
+
attr_reader :type_name, :balance_as_of, :created_at
|
14
|
+
|
15
|
+
sig { returns(String) }
|
16
|
+
attr_accessor :name, :balance, :currency
|
17
|
+
|
18
|
+
sig { returns(T.nilable(String)) }
|
19
|
+
attr_accessor :display_name, :closed_on, :institution_name, :subtype_name
|
20
|
+
|
21
|
+
sig { returns(T::Boolean) }
|
22
|
+
attr_accessor :exclude_transactions
|
23
|
+
|
24
|
+
# Valid asset type names
|
25
|
+
VALID_TYPE_NAMES = T.let(
|
26
|
+
[
|
27
|
+
"cash",
|
28
|
+
"credit",
|
29
|
+
"investment",
|
30
|
+
"real estate",
|
31
|
+
"loan",
|
32
|
+
"vehicle",
|
33
|
+
"cryptocurrency",
|
34
|
+
"employee compensation",
|
35
|
+
"other liability",
|
36
|
+
"other asset",
|
37
|
+
],
|
38
|
+
T::Array[String],
|
39
|
+
)
|
40
|
+
|
41
|
+
sig do
|
42
|
+
params(
|
43
|
+
created_at: String,
|
44
|
+
type_name: String,
|
45
|
+
name: String,
|
46
|
+
balance: String,
|
47
|
+
balance_as_of: String,
|
48
|
+
currency: String,
|
49
|
+
exclude_transactions: T::Boolean,
|
50
|
+
id: Integer,
|
51
|
+
subtype_name: T.nilable(String),
|
52
|
+
display_name: T.nilable(String),
|
53
|
+
closed_on: T.nilable(String),
|
54
|
+
institution_name: T.nilable(String),
|
55
|
+
).void
|
56
|
+
end
|
57
|
+
def initialize(created_at:, type_name:, name:, balance:, balance_as_of:, currency:, exclude_transactions:, id:,
|
58
|
+
subtype_name: nil, display_name: nil, closed_on: nil, institution_name: nil)
|
59
|
+
super()
|
60
|
+
@created_at = T.let(validate_iso8601!(created_at), String)
|
61
|
+
@type_name = T.let(validate_one_of!(type_name, VALID_TYPE_NAMES), String)
|
62
|
+
@name = name
|
63
|
+
@balance = balance
|
64
|
+
@balance_as_of = T.let(validate_iso8601!(balance_as_of), String)
|
65
|
+
@currency = currency
|
66
|
+
@exclude_transactions = exclude_transactions
|
67
|
+
@id = id
|
68
|
+
@subtype_name = subtype_name
|
69
|
+
@display_name = display_name
|
70
|
+
@closed_on = closed_on
|
71
|
+
@institution_name = institution_name
|
72
|
+
end
|
73
|
+
|
74
|
+
sig { params(name: String).void }
|
75
|
+
def type_name=(name)
|
76
|
+
@type_name = validate_one_of!(name, VALID_TYPE_NAMES)
|
77
|
+
end
|
78
|
+
|
79
|
+
sig { params(time: String).void }
|
80
|
+
def balance_as_of=(time)
|
81
|
+
@balance_as_of = validate_iso8601!(time)
|
82
|
+
end
|
83
|
+
|
84
|
+
sig { params(time: String).void }
|
85
|
+
def created_at=(time)
|
86
|
+
@created_at = validate_iso8601!(time)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|