lunchmoney 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|