lunchmoney 1.0.0 → 1.1.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 +4 -4
- data/.github/workflows/publish_gem.yml +1 -0
- data/Gemfile.lock +1 -1
- data/lib/lunchmoney/api.rb +34 -34
- data/lib/lunchmoney/calls/assets.rb +98 -0
- data/lib/lunchmoney/calls/base.rb +112 -0
- data/lib/lunchmoney/calls/budgets.rb +84 -0
- data/lib/lunchmoney/calls/categories.rb +196 -0
- data/lib/lunchmoney/calls/crypto.rb +50 -0
- data/lib/lunchmoney/calls/plaid_accounts.rb +40 -0
- data/lib/lunchmoney/calls/recurring_expenses.rb +29 -0
- data/lib/lunchmoney/calls/tags.rb +21 -0
- data/lib/lunchmoney/calls/transactions.rb +220 -0
- data/lib/lunchmoney/calls/users.rb +21 -0
- data/lib/lunchmoney/objects/asset.rb +91 -0
- data/lib/lunchmoney/objects/budget.rb +76 -0
- data/lib/lunchmoney/objects/category.rb +55 -0
- data/lib/lunchmoney/objects/child_category.rb +44 -0
- data/lib/lunchmoney/objects/child_transaction.rb +35 -0
- data/lib/lunchmoney/objects/config.rb +40 -0
- data/lib/lunchmoney/objects/crypto.rb +46 -0
- data/lib/lunchmoney/objects/crypto_base.rb +67 -0
- data/lib/lunchmoney/objects/data.rb +44 -0
- data/lib/lunchmoney/objects/object.rb +28 -0
- data/lib/lunchmoney/objects/plaid_account.rb +75 -0
- data/lib/lunchmoney/objects/recurring_expense.rb +68 -0
- data/lib/lunchmoney/objects/recurring_expense_base.rb +31 -0
- data/lib/lunchmoney/objects/split.rb +28 -0
- data/lib/lunchmoney/objects/tag.rb +24 -0
- data/lib/lunchmoney/objects/tag_base.rb +23 -0
- data/lib/lunchmoney/objects/transaction.rb +160 -0
- data/lib/lunchmoney/objects/transaction_base.rb +54 -0
- data/lib/lunchmoney/objects/transaction_modification_base.rb +32 -0
- data/lib/lunchmoney/objects/update_transaction.rb +47 -0
- data/lib/lunchmoney/objects/user.rb +38 -0
- data/lib/lunchmoney/version.rb +1 -1
- metadata +32 -32
- data/lib/lunchmoney/api_call.rb +0 -109
- data/lib/lunchmoney/assets/asset.rb +0 -89
- data/lib/lunchmoney/assets/asset_calls.rb +0 -96
- data/lib/lunchmoney/budget/budget.rb +0 -74
- data/lib/lunchmoney/budget/budget_calls.rb +0 -82
- data/lib/lunchmoney/budget/config.rb +0 -38
- data/lib/lunchmoney/budget/data.rb +0 -42
- data/lib/lunchmoney/categories/category/category.rb +0 -52
- data/lib/lunchmoney/categories/category/child_category.rb +0 -42
- data/lib/lunchmoney/categories/category_calls.rb +0 -195
- data/lib/lunchmoney/crypto/crypto/crypto.rb +0 -43
- data/lib/lunchmoney/crypto/crypto/crypto_base.rb +0 -65
- data/lib/lunchmoney/crypto/crypto_calls.rb +0 -49
- data/lib/lunchmoney/data_object.rb +0 -25
- data/lib/lunchmoney/plaid_accounts/plaid_account.rb +0 -73
- data/lib/lunchmoney/plaid_accounts/plaid_account_calls.rb +0 -38
- data/lib/lunchmoney/recurring_expenses/recurring_expense/recurring_expense.rb +0 -65
- data/lib/lunchmoney/recurring_expenses/recurring_expense/recurring_expense_base.rb +0 -29
- data/lib/lunchmoney/recurring_expenses/recurring_expense_calls.rb +0 -28
- data/lib/lunchmoney/tags/tag/tag.rb +0 -20
- data/lib/lunchmoney/tags/tag/tag_base.rb +0 -21
- data/lib/lunchmoney/tags/tag_calls.rb +0 -20
- data/lib/lunchmoney/transactions/transaction/child_transaction.rb +0 -31
- data/lib/lunchmoney/transactions/transaction/split.rb +0 -24
- data/lib/lunchmoney/transactions/transaction/transaction.rb +0 -156
- data/lib/lunchmoney/transactions/transaction/transaction_base.rb +0 -52
- data/lib/lunchmoney/transactions/transaction/transaction_modification_base.rb +0 -30
- data/lib/lunchmoney/transactions/transaction/update_transaction.rb +0 -43
- data/lib/lunchmoney/transactions/transaction_calls.rb +0 -218
- data/lib/lunchmoney/user/user.rb +0 -36
- data/lib/lunchmoney/user/user_calls.rb +0 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6a7fc6df7ade58a32f0302f4faab00cf1f25ad4baa74960373a7d05870f55d47
|
4
|
+
data.tar.gz: 014e04789ba5f352ccd884f2420f0ba6fdb6fed38728977071f2c8ec43dedc24
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 181b9a3fe5d781b420e337c3504495374a47515b5cd50237d1eec4e9b0d3b13f7c6c8180a595cd11b09c4d02678df9f480c76f60fad933562235d6bcd022ac73
|
7
|
+
data.tar.gz: 7ac6dc11c6519049d4c2591bdb41e7fc0c471f7ace22ffc08f841d0c0d1ca5fc1995244baabbe8ce0b8157d7af812b37f3fe1cf581c80f84fcbccdf0710dd2cd
|
data/Gemfile.lock
CHANGED
data/lib/lunchmoney/api.rb
CHANGED
@@ -4,21 +4,21 @@
|
|
4
4
|
require_relative "exceptions"
|
5
5
|
require_relative "configuration"
|
6
6
|
|
7
|
-
require_relative "
|
8
|
-
require_relative "
|
9
|
-
require_relative "
|
10
|
-
require_relative "categories
|
11
|
-
require_relative "tags
|
12
|
-
require_relative "transactions
|
13
|
-
require_relative "recurring_expenses
|
14
|
-
require_relative "
|
15
|
-
require_relative "assets
|
16
|
-
require_relative "plaid_accounts
|
17
|
-
require_relative "crypto
|
7
|
+
require_relative "calls/base"
|
8
|
+
require_relative "objects/object"
|
9
|
+
require_relative "calls/users"
|
10
|
+
require_relative "calls/categories"
|
11
|
+
require_relative "calls/tags"
|
12
|
+
require_relative "calls/transactions"
|
13
|
+
require_relative "calls/recurring_expenses"
|
14
|
+
require_relative "calls/budgets"
|
15
|
+
require_relative "calls/assets"
|
16
|
+
require_relative "calls/plaid_accounts"
|
17
|
+
require_relative "calls/crypto"
|
18
18
|
|
19
19
|
module LunchMoney
|
20
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::
|
21
|
+
# so that it is never necessary to go through things like `LunchMoney::Calls::Users.new.user` instead you can directly
|
22
22
|
# call the endpoint with LunchMoney::Api.new.user and it will be delegated to the correct call.
|
23
23
|
class Api
|
24
24
|
sig { returns(T.nilable(String)) }
|
@@ -31,10 +31,10 @@ module LunchMoney
|
|
31
31
|
|
32
32
|
delegate :me, to: :user_calls
|
33
33
|
|
34
|
-
sig { returns(LunchMoney::
|
34
|
+
sig { returns(LunchMoney::Calls::Base) }
|
35
35
|
def user_calls
|
36
36
|
with_valid_api_key do
|
37
|
-
@user_calls ||= T.let(LunchMoney::
|
37
|
+
@user_calls ||= T.let(LunchMoney::Calls::Users.new(api_key:), T.nilable(LunchMoney::Calls::Users))
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
@@ -48,19 +48,19 @@ module LunchMoney
|
|
48
48
|
:force_delete_category,
|
49
49
|
to: :category_calls
|
50
50
|
|
51
|
-
sig { returns(LunchMoney::
|
51
|
+
sig { returns(LunchMoney::Calls::Base) }
|
52
52
|
def category_calls
|
53
53
|
with_valid_api_key do
|
54
|
-
@category_calls ||= T.let(LunchMoney::
|
54
|
+
@category_calls ||= T.let(LunchMoney::Calls::Categories.new(api_key:), T.nilable(LunchMoney::Calls::Categories))
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
58
58
|
delegate :tags, to: :tag_calls
|
59
59
|
|
60
|
-
sig { returns(LunchMoney::
|
60
|
+
sig { returns(LunchMoney::Calls::Base) }
|
61
61
|
def tag_calls
|
62
62
|
with_valid_api_key do
|
63
|
-
@tag_calls ||= T.let(LunchMoney::
|
63
|
+
@tag_calls ||= T.let(LunchMoney::Calls::Tags.new(api_key:), T.nilable(LunchMoney::Calls::Tags))
|
64
64
|
end
|
65
65
|
end
|
66
66
|
|
@@ -74,70 +74,70 @@ module LunchMoney
|
|
74
74
|
:delete_transaction_group,
|
75
75
|
to: :transaction_calls
|
76
76
|
|
77
|
-
sig { returns(LunchMoney::
|
77
|
+
sig { returns(LunchMoney::Calls::Base) }
|
78
78
|
def transaction_calls
|
79
79
|
with_valid_api_key do
|
80
80
|
@transaction_calls ||= T.let(
|
81
|
-
LunchMoney::
|
82
|
-
T.nilable(LunchMoney::
|
81
|
+
LunchMoney::Calls::Transactions.new(api_key:),
|
82
|
+
T.nilable(LunchMoney::Calls::Transactions),
|
83
83
|
)
|
84
84
|
end
|
85
85
|
end
|
86
86
|
|
87
87
|
delegate :recurring_expenses, to: :recurring_expense_calls
|
88
88
|
|
89
|
-
sig { returns(LunchMoney::
|
89
|
+
sig { returns(LunchMoney::Calls::Base) }
|
90
90
|
def recurring_expense_calls
|
91
91
|
with_valid_api_key do
|
92
92
|
@recurring_expense_calls ||= T.let(
|
93
|
-
LunchMoney::
|
94
|
-
T.nilable(LunchMoney::
|
93
|
+
LunchMoney::Calls::RecurringExpenses.new(api_key:),
|
94
|
+
T.nilable(LunchMoney::Calls::RecurringExpenses),
|
95
95
|
)
|
96
96
|
end
|
97
97
|
end
|
98
98
|
|
99
99
|
delegate :budgets, :upsert_budget, :remove_budget, to: :budget_calls
|
100
100
|
|
101
|
-
sig { returns(LunchMoney::
|
101
|
+
sig { returns(LunchMoney::Calls::Base) }
|
102
102
|
def budget_calls
|
103
103
|
with_valid_api_key do
|
104
|
-
@budget_calls ||= T.let(LunchMoney::
|
104
|
+
@budget_calls ||= T.let(LunchMoney::Calls::Budgets.new(api_key:), T.nilable(LunchMoney::Calls::Budgets))
|
105
105
|
end
|
106
106
|
end
|
107
107
|
|
108
108
|
delegate :assets, :create_asset, :update_asset, to: :asset_calls
|
109
109
|
|
110
|
-
sig { returns(LunchMoney::
|
110
|
+
sig { returns(LunchMoney::Calls::Base) }
|
111
111
|
def asset_calls
|
112
112
|
with_valid_api_key do
|
113
|
-
@asset_calls ||= T.let(LunchMoney::
|
113
|
+
@asset_calls ||= T.let(LunchMoney::Calls::Assets.new(api_key:), T.nilable(LunchMoney::Calls::Assets))
|
114
114
|
end
|
115
115
|
end
|
116
116
|
|
117
117
|
delegate :plaid_accounts, :plaid_accounts_fetch, to: :plaid_account_calls
|
118
118
|
|
119
|
-
sig { returns(LunchMoney::
|
119
|
+
sig { returns(LunchMoney::Calls::Base) }
|
120
120
|
def plaid_account_calls
|
121
121
|
with_valid_api_key do
|
122
122
|
@plaid_account_calls ||= T.let(
|
123
|
-
LunchMoney::
|
124
|
-
T.nilable(LunchMoney::
|
123
|
+
LunchMoney::Calls::PlaidAccounts.new(api_key:),
|
124
|
+
T.nilable(LunchMoney::Calls::PlaidAccounts),
|
125
125
|
)
|
126
126
|
end
|
127
127
|
end
|
128
128
|
|
129
129
|
delegate :crypto, :update_crypto, to: :crypto_calls
|
130
130
|
|
131
|
-
sig { returns(LunchMoney::
|
131
|
+
sig { returns(LunchMoney::Calls::Base) }
|
132
132
|
def crypto_calls
|
133
133
|
with_valid_api_key do
|
134
|
-
@crypto_calls ||= T.let(LunchMoney::
|
134
|
+
@crypto_calls ||= T.let(LunchMoney::Calls::Crypto.new(api_key:), T.nilable(LunchMoney::Calls::Crypto))
|
135
135
|
end
|
136
136
|
end
|
137
137
|
|
138
138
|
private
|
139
139
|
|
140
|
-
sig { params(block: T.proc.returns(LunchMoney::
|
140
|
+
sig { params(block: T.proc.returns(LunchMoney::Calls::Base)).returns(LunchMoney::Calls::Base) }
|
141
141
|
def with_valid_api_key(&block)
|
142
142
|
raise(InvalidApiKey, "API key is missing or invalid") if api_key.blank?
|
143
143
|
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative "../objects/asset"
|
5
|
+
|
6
|
+
module LunchMoney
|
7
|
+
module Calls
|
8
|
+
# https://lunchmoney.dev/#assets
|
9
|
+
class Assets < LunchMoney::Calls::Base
|
10
|
+
sig { returns(T.any(T::Array[LunchMoney::Objects::Asset], LunchMoney::Errors)) }
|
11
|
+
def assets
|
12
|
+
response = get("assets")
|
13
|
+
|
14
|
+
api_errors = errors(response)
|
15
|
+
return api_errors if api_errors.present?
|
16
|
+
|
17
|
+
response.body[:assets].map do |asset|
|
18
|
+
LunchMoney::Objects::Asset.new(**asset)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
sig do
|
23
|
+
params(
|
24
|
+
type_name: String,
|
25
|
+
name: String,
|
26
|
+
balance: String,
|
27
|
+
subtype_name: T.nilable(String),
|
28
|
+
display_name: T.nilable(String),
|
29
|
+
balance_as_of: T.nilable(String),
|
30
|
+
currency: T.nilable(String),
|
31
|
+
institution_name: T.nilable(String),
|
32
|
+
closed_on: T.nilable(String),
|
33
|
+
exclude_transactions: T.nilable(T::Boolean),
|
34
|
+
).returns(T.any(LunchMoney::Objects::Asset, LunchMoney::Errors))
|
35
|
+
end
|
36
|
+
def create_asset(type_name:, name:, balance:, subtype_name: nil, display_name: nil, balance_as_of: nil,
|
37
|
+
currency: nil, institution_name: nil, closed_on: nil, exclude_transactions: nil)
|
38
|
+
params = {
|
39
|
+
type_name:,
|
40
|
+
name:,
|
41
|
+
balance:,
|
42
|
+
subtype_name:,
|
43
|
+
display_name:,
|
44
|
+
balance_as_of:,
|
45
|
+
currency:,
|
46
|
+
institution_name:,
|
47
|
+
closed_on:,
|
48
|
+
exclude_transactions:,
|
49
|
+
}
|
50
|
+
|
51
|
+
response = post("assets", params)
|
52
|
+
|
53
|
+
api_errors = errors(response)
|
54
|
+
return api_errors if api_errors.present?
|
55
|
+
|
56
|
+
LunchMoney::Objects::Asset.new(**response.body)
|
57
|
+
end
|
58
|
+
|
59
|
+
sig do
|
60
|
+
params(
|
61
|
+
asset_id: Integer,
|
62
|
+
type_name: T.nilable(String),
|
63
|
+
name: T.nilable(String),
|
64
|
+
balance: T.nilable(String),
|
65
|
+
subtype_name: T.nilable(String),
|
66
|
+
display_name: T.nilable(String),
|
67
|
+
balance_as_of: T.nilable(String),
|
68
|
+
currency: T.nilable(String),
|
69
|
+
institution_name: T.nilable(String),
|
70
|
+
closed_on: T.nilable(String),
|
71
|
+
exclude_transactions: T.nilable(T::Boolean),
|
72
|
+
).returns(T.any(LunchMoney::Objects::Asset, LunchMoney::Errors))
|
73
|
+
end
|
74
|
+
def update_asset(asset_id, type_name: nil, name: nil, balance: nil, subtype_name: nil, display_name: nil,
|
75
|
+
balance_as_of: nil, currency: nil, institution_name: nil, closed_on: nil, exclude_transactions: nil)
|
76
|
+
params = {
|
77
|
+
type_name:,
|
78
|
+
name:,
|
79
|
+
balance:,
|
80
|
+
subtype_name:,
|
81
|
+
display_name:,
|
82
|
+
balance_as_of:,
|
83
|
+
currency:,
|
84
|
+
institution_name:,
|
85
|
+
closed_on:,
|
86
|
+
exclude_transactions:,
|
87
|
+
}
|
88
|
+
|
89
|
+
response = put("assets/#{asset_id}", params)
|
90
|
+
|
91
|
+
api_errors = errors(response)
|
92
|
+
return api_errors if api_errors.present?
|
93
|
+
|
94
|
+
LunchMoney::Objects::Asset.new(**response.body)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative "../errors"
|
5
|
+
|
6
|
+
module LunchMoney
|
7
|
+
# Namespace for API call classes
|
8
|
+
module Calls
|
9
|
+
# Base class for all API call types
|
10
|
+
class Base
|
11
|
+
# Base URL used for API calls
|
12
|
+
BASE_URL = "https://dev.lunchmoney.app/v1/"
|
13
|
+
|
14
|
+
sig { returns(T.nilable(String)) }
|
15
|
+
attr_reader :api_key
|
16
|
+
|
17
|
+
sig { params(api_key: T.nilable(String)).void }
|
18
|
+
def initialize(api_key: nil)
|
19
|
+
@api_key = T.let((api_key || LunchMoney.configuration.api_key), T.nilable(String))
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
sig { params(endpoint: String, query_params: T.nilable(T::Hash[Symbol, T.untyped])).returns(Faraday::Response) }
|
25
|
+
def get(endpoint, query_params: nil)
|
26
|
+
connection = request(flat_params: true)
|
27
|
+
|
28
|
+
if query_params.present?
|
29
|
+
connection.get(BASE_URL + endpoint, query_params)
|
30
|
+
else
|
31
|
+
connection.get(BASE_URL + endpoint)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
sig { params(endpoint: String, params: T.nilable(T::Hash[Symbol, T.untyped])).returns(Faraday::Response) }
|
36
|
+
def post(endpoint, params)
|
37
|
+
request(json_request: true).post(BASE_URL + endpoint, params)
|
38
|
+
end
|
39
|
+
|
40
|
+
sig { params(endpoint: String, body: T::Hash[Symbol, T.untyped]).returns(Faraday::Response) }
|
41
|
+
def put(endpoint, body)
|
42
|
+
request(json_request: true).put(BASE_URL + endpoint) do |req|
|
43
|
+
req.body = body
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
sig { params(endpoint: String, query_params: T.nilable(T::Hash[Symbol, T.untyped])).returns(Faraday::Response) }
|
48
|
+
def delete(endpoint, query_params: nil)
|
49
|
+
connection = request(flat_params: true)
|
50
|
+
|
51
|
+
if query_params.present?
|
52
|
+
connection.delete(BASE_URL + endpoint, query_params)
|
53
|
+
else
|
54
|
+
connection.delete(BASE_URL + endpoint)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
sig { params(json_request: T::Boolean, flat_params: T::Boolean).returns(Faraday::Connection) }
|
59
|
+
def request(json_request: false, flat_params: false)
|
60
|
+
Faraday.new do |conn|
|
61
|
+
conn.request(:authorization, "Bearer", @api_key)
|
62
|
+
conn.request(:json) if json_request
|
63
|
+
# conn.options.params_encoder = Faraday::FlatParamsEncoder if flat_params
|
64
|
+
conn.response(:json, content_type: /json$/, parser_options: { symbolize_names: true })
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
sig { params(response: Faraday::Response).returns(LunchMoney::Errors) }
|
69
|
+
def errors(response)
|
70
|
+
body = response.body
|
71
|
+
|
72
|
+
return parse_errors(body) unless error_hash(body).nil?
|
73
|
+
|
74
|
+
LunchMoney::Errors.new
|
75
|
+
end
|
76
|
+
|
77
|
+
sig { params(body: T::Hash[Symbol, T.any(String, T::Array[String])]).returns(LunchMoney::Errors) }
|
78
|
+
def parse_errors(body)
|
79
|
+
errors = error_hash(body)
|
80
|
+
api_errors = LunchMoney::Errors.new
|
81
|
+
return api_errors if errors.blank?
|
82
|
+
|
83
|
+
case errors
|
84
|
+
when String
|
85
|
+
api_errors << errors
|
86
|
+
when Array
|
87
|
+
errors.each { |error| api_errors << error }
|
88
|
+
end
|
89
|
+
|
90
|
+
api_errors
|
91
|
+
end
|
92
|
+
|
93
|
+
sig { params(body: T.untyped).returns(T.untyped) }
|
94
|
+
def error_hash(body)
|
95
|
+
return unless body.is_a?(Hash)
|
96
|
+
|
97
|
+
if body[:error]
|
98
|
+
body[:error]
|
99
|
+
elsif body[:errors]
|
100
|
+
body[:errors]
|
101
|
+
elsif body[:name] == "Error"
|
102
|
+
body[:message]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
sig { params(params: T::Hash[Symbol, T.untyped]).returns(T::Hash[Symbol, T.untyped]) }
|
107
|
+
def clean_params(params)
|
108
|
+
params.reject! { |_key, value| value.nil? }
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative "../objects/budget"
|
5
|
+
|
6
|
+
module LunchMoney
|
7
|
+
module Calls
|
8
|
+
# https://lunchmoney.dev/#budget
|
9
|
+
class Budgets < LunchMoney::Calls::Base
|
10
|
+
sig do
|
11
|
+
params(
|
12
|
+
start_date: String,
|
13
|
+
end_date: String,
|
14
|
+
currency: T.nilable(String),
|
15
|
+
).returns(T.any(T::Array[LunchMoney::Objects::Budget], LunchMoney::Errors))
|
16
|
+
end
|
17
|
+
def budgets(start_date:, end_date:, currency: nil)
|
18
|
+
params = clean_params({ start_date:, end_date:, currency: })
|
19
|
+
response = get("budgets", query_params: params)
|
20
|
+
|
21
|
+
api_errors = errors(response)
|
22
|
+
return api_errors if api_errors.present?
|
23
|
+
|
24
|
+
response.body.map do |budget|
|
25
|
+
if budget[:data]
|
26
|
+
data_keys = budget[:data].keys
|
27
|
+
data_keys.each do |data_key|
|
28
|
+
budget[:data][data_key] = LunchMoney::Objects::Data.new(**budget[:data][data_key])
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
if budget[:config]
|
33
|
+
config_keys = budget[:config].keys
|
34
|
+
config_keys.each do |config_key|
|
35
|
+
budget[:config][config_key] = LunchMoney::Objects::Data.new(**budget[:config][config_key])
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
if budget[:recurring]
|
40
|
+
budget[:recurring][:list]&.map! { |recurring| LunchMoney::Objects::RecurringExpenseBase.new(**recurring) }
|
41
|
+
end
|
42
|
+
|
43
|
+
LunchMoney::Objects::Budget.new(**budget)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
sig do
|
48
|
+
params(
|
49
|
+
start_date: String,
|
50
|
+
category_id: Integer,
|
51
|
+
amount: Number,
|
52
|
+
currency: T.nilable(String),
|
53
|
+
).returns(T.any(
|
54
|
+
T::Hash[Symbol, { category_id: Integer, amount: Number, currency: String, start_date: String }],
|
55
|
+
LunchMoney::Errors,
|
56
|
+
))
|
57
|
+
end
|
58
|
+
def upsert_budget(start_date:, category_id:, amount:, currency: nil)
|
59
|
+
params = clean_params({ start_date:, category_id:, amount:, currency: })
|
60
|
+
response = put("budgets", params)
|
61
|
+
|
62
|
+
api_errors = errors(response)
|
63
|
+
return api_errors if api_errors.present?
|
64
|
+
|
65
|
+
response.body
|
66
|
+
end
|
67
|
+
|
68
|
+
sig { params(start_date: String, category_id: Integer).returns(T.any(T::Boolean, LunchMoney::Errors)) }
|
69
|
+
def remove_budget(start_date:, category_id:)
|
70
|
+
params = {
|
71
|
+
start_date:,
|
72
|
+
category_id:,
|
73
|
+
}
|
74
|
+
|
75
|
+
response = delete("budgets", query_params: params)
|
76
|
+
|
77
|
+
api_errors = errors(response)
|
78
|
+
return api_errors if api_errors.present?
|
79
|
+
|
80
|
+
response.body
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,196 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative "../objects/category"
|
5
|
+
|
6
|
+
module LunchMoney
|
7
|
+
module Calls
|
8
|
+
# https://lunchmoney.dev/#categories
|
9
|
+
class Categories < LunchMoney::Calls::Base
|
10
|
+
# Valid query parameter formets for categories
|
11
|
+
VALID_FORMATS = T.let(
|
12
|
+
[
|
13
|
+
"flattened",
|
14
|
+
"nested",
|
15
|
+
],
|
16
|
+
T::Array[String],
|
17
|
+
)
|
18
|
+
|
19
|
+
sig do
|
20
|
+
params(
|
21
|
+
format: T.nilable(T.any(String, Symbol)),
|
22
|
+
).returns(T.any(T::Array[LunchMoney::Objects::Category], LunchMoney::Errors))
|
23
|
+
end
|
24
|
+
def categories(format: nil)
|
25
|
+
response = get("categories", query_params: categories_params(format:))
|
26
|
+
|
27
|
+
api_errors = errors(response)
|
28
|
+
return api_errors if api_errors.present?
|
29
|
+
|
30
|
+
response.body[:categories].map do |category|
|
31
|
+
category[:children]&.map! { |child_category| LunchMoney::Objects::Category.new(**child_category) }
|
32
|
+
|
33
|
+
LunchMoney::Objects::Category.new(**category)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
sig { params(category_id: Integer).returns(T.any(LunchMoney::Objects::Category, LunchMoney::Errors)) }
|
38
|
+
def category(category_id)
|
39
|
+
response = get("categories/#{category_id}")
|
40
|
+
|
41
|
+
api_errors = errors(response)
|
42
|
+
return api_errors if api_errors.present?
|
43
|
+
|
44
|
+
response.body[:children]&.map! { |child_category| LunchMoney::Objects::ChildCategory.new(**child_category) }
|
45
|
+
|
46
|
+
LunchMoney::Objects::Category.new(**response.body)
|
47
|
+
end
|
48
|
+
|
49
|
+
sig do
|
50
|
+
params(
|
51
|
+
name: String,
|
52
|
+
description: T.nilable(String),
|
53
|
+
is_income: T::Boolean,
|
54
|
+
exclude_from_budget: T::Boolean,
|
55
|
+
exclude_from_totals: T::Boolean,
|
56
|
+
archived: T::Boolean,
|
57
|
+
group_id: T.nilable(Integer),
|
58
|
+
).returns(T.any(T::Hash[Symbol, Integer], LunchMoney::Errors))
|
59
|
+
end
|
60
|
+
def create_category(name:, description: nil, is_income: false, exclude_from_budget: false,
|
61
|
+
exclude_from_totals: false, archived: false, group_id: nil)
|
62
|
+
params = clean_params({
|
63
|
+
name:,
|
64
|
+
description:,
|
65
|
+
is_income:,
|
66
|
+
exclude_from_budget:,
|
67
|
+
exclude_from_totals:,
|
68
|
+
archived:,
|
69
|
+
group_id:,
|
70
|
+
})
|
71
|
+
response = post("categories", params)
|
72
|
+
|
73
|
+
api_errors = errors(response)
|
74
|
+
return api_errors if api_errors.present?
|
75
|
+
|
76
|
+
response.body
|
77
|
+
end
|
78
|
+
|
79
|
+
sig do
|
80
|
+
params(
|
81
|
+
name: String,
|
82
|
+
description: T.nilable(String),
|
83
|
+
is_income: T::Boolean,
|
84
|
+
exclude_from_budget: T::Boolean,
|
85
|
+
exclude_from_totals: T::Boolean,
|
86
|
+
category_ids: T::Array[Integer],
|
87
|
+
new_categories: T::Array[String],
|
88
|
+
).returns(T.any(T::Hash[Symbol, Integer], LunchMoney::Errors))
|
89
|
+
end
|
90
|
+
def create_category_group(name:, description: nil, is_income: false, exclude_from_budget: false,
|
91
|
+
exclude_from_totals: false, category_ids: [], new_categories: [])
|
92
|
+
|
93
|
+
params = {
|
94
|
+
name:,
|
95
|
+
description:,
|
96
|
+
is_income:,
|
97
|
+
exclude_from_budget:,
|
98
|
+
exclude_from_totals:,
|
99
|
+
category_ids:,
|
100
|
+
new_categories:,
|
101
|
+
}
|
102
|
+
|
103
|
+
response = post("categories/group", params)
|
104
|
+
|
105
|
+
api_errors = errors(response)
|
106
|
+
return api_errors if api_errors.present?
|
107
|
+
|
108
|
+
response.body
|
109
|
+
end
|
110
|
+
|
111
|
+
sig do
|
112
|
+
params(
|
113
|
+
category_id: Integer,
|
114
|
+
name: T.nilable(String),
|
115
|
+
description: T.nilable(String),
|
116
|
+
is_income: T.nilable(T::Boolean),
|
117
|
+
exclude_from_budget: T.nilable(T::Boolean),
|
118
|
+
exclude_from_totals: T.nilable(T::Boolean),
|
119
|
+
archived: T.nilable(T::Boolean),
|
120
|
+
group_id: T.nilable(Integer),
|
121
|
+
).returns(T.any(T::Boolean, LunchMoney::Errors))
|
122
|
+
end
|
123
|
+
def update_category(category_id, name: nil, description: nil, is_income: nil, exclude_from_budget: nil,
|
124
|
+
exclude_from_totals: nil, archived: nil, group_id: nil)
|
125
|
+
|
126
|
+
params = clean_params({
|
127
|
+
name:,
|
128
|
+
description:,
|
129
|
+
is_income:,
|
130
|
+
exclude_from_budget:,
|
131
|
+
exclude_from_totals:,
|
132
|
+
archived:,
|
133
|
+
group_id:,
|
134
|
+
})
|
135
|
+
response = put("categories/#{category_id}", params)
|
136
|
+
|
137
|
+
api_errors = errors(response)
|
138
|
+
return api_errors if api_errors.present?
|
139
|
+
|
140
|
+
response.body
|
141
|
+
end
|
142
|
+
|
143
|
+
sig do
|
144
|
+
params(
|
145
|
+
group_id: Integer,
|
146
|
+
category_ids: T::Array[Integer],
|
147
|
+
new_categories: T::Array[String],
|
148
|
+
).returns(T.any(LunchMoney::Objects::Category, LunchMoney::Errors))
|
149
|
+
end
|
150
|
+
def add_to_category_group(group_id, category_ids: [], new_categories: [])
|
151
|
+
params = {
|
152
|
+
category_ids:,
|
153
|
+
new_categories:,
|
154
|
+
}
|
155
|
+
|
156
|
+
response = post("categories/group/#{group_id}/add", params)
|
157
|
+
|
158
|
+
api_errors = errors(response)
|
159
|
+
return api_errors if api_errors.present?
|
160
|
+
|
161
|
+
LunchMoney::Objects::Category.new(**response.body)
|
162
|
+
end
|
163
|
+
|
164
|
+
sig { params(category_id: Integer).returns(T.any(T::Boolean, LunchMoney::Errors)) }
|
165
|
+
def delete_category(category_id)
|
166
|
+
response = delete("categories/#{category_id}")
|
167
|
+
|
168
|
+
api_errors = errors(response)
|
169
|
+
return api_errors if api_errors.present?
|
170
|
+
|
171
|
+
response.body
|
172
|
+
end
|
173
|
+
|
174
|
+
sig { params(category_id: Integer).returns(T.any(T::Boolean, LunchMoney::Errors)) }
|
175
|
+
def force_delete_category(category_id)
|
176
|
+
response = delete("categories/#{category_id}/force")
|
177
|
+
|
178
|
+
api_errors = errors(response)
|
179
|
+
return api_errors if api_errors.present?
|
180
|
+
|
181
|
+
response.body
|
182
|
+
end
|
183
|
+
|
184
|
+
private
|
185
|
+
|
186
|
+
sig { params(format: T.nilable(T.any(String, Symbol))).returns(T.nilable(T::Hash[Symbol, String])) }
|
187
|
+
def categories_params(format:)
|
188
|
+
return unless format
|
189
|
+
|
190
|
+
raise(InvalidQueryParameter, "format must be either flattened or nested") if VALID_FORMATS.exclude?(format.to_s)
|
191
|
+
|
192
|
+
{ format: format.to_s }
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|