monzo-cli 0.0.1

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.
@@ -0,0 +1,50 @@
1
+ require 'multi_json'
2
+
3
+ module Mondo
4
+ class Error < StandardError
5
+ end
6
+
7
+ class ApiError < Error
8
+ attr_reader :response, :code, :description
9
+
10
+ def initialize(response)
11
+ @response = response
12
+ @code = response.status
13
+
14
+ puts response.inspect
15
+
16
+ begin
17
+ parsed_response = MultiJson.decode(response.body)
18
+ errors = parsed_response["message"]
19
+ @description = stringify_errors(errors)
20
+ rescue MultiJson::ParseError
21
+ @description = response.body ? response.body.strip : "Unknown error"
22
+ end
23
+ end
24
+
25
+ def to_s
26
+ "#{super}. #{self.description}"
27
+ end
28
+
29
+ private
30
+
31
+ def stringify_errors(errors)
32
+ case errors
33
+ when Array
34
+ errors.join(", ")
35
+ when Hash
36
+ errors.flat_map do |field, messages|
37
+ messages.map { |message| "#{field} #{message}" }
38
+ end.join(", ")
39
+ else
40
+ errors.to_s
41
+ end
42
+ end
43
+ end
44
+
45
+ class ClientError < Error
46
+ end
47
+
48
+ class SignatureError < Error
49
+ end
50
+ end
@@ -0,0 +1,31 @@
1
+ module Mondo
2
+ class FeedItem < Resource
3
+
4
+ attr_accessor :title,
5
+ :image_url,
6
+ :url, # TODO - make me consistent when the Mondo API changes
7
+ :background_color,
8
+ :body,
9
+ :type
10
+
11
+
12
+ # temporary fix until the API accepts JSON data in the request body
13
+ def save
14
+ self.client.api_post("/feed", self.create_params)
15
+ end
16
+
17
+ def create_params
18
+ {
19
+ account_id: self.client.account_id,
20
+ type: "basic",
21
+ url: self.url,
22
+ params: {
23
+ title: self.title,
24
+ image_url: self.image_url,
25
+ background_color: self.background_color,
26
+ body: self.body
27
+ }
28
+ }
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,15 @@
1
+ module Mondo
2
+ class Merchant < Resource
3
+
4
+ attr_accessor :id, :group_id, :logo, :name, :raw_data, :address, :emoji
5
+
6
+ boolean_accessor :online, :is_load, :settled
7
+
8
+ date_accessor :created
9
+
10
+ def address
11
+ ::Mondo::Address.new(raw_data['address'], self.client)
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,52 @@
1
+ module Mondo
2
+ class Resource
3
+
4
+ attr_accessor :client, :raw_data
5
+
6
+ def initialize(hash={}, client)
7
+ self.raw_data = hash
8
+ self.client = client
9
+ hash.each { |key,val| send("#{key}=", val) if respond_to?("#{key}=") }
10
+ self.to_s
11
+ end
12
+
13
+ def to_s
14
+ "#<#{self.class} #{raw_data}>"
15
+ end
16
+
17
+ def inspect
18
+ self.to_s
19
+ end
20
+
21
+ class << self
22
+ def date_writer(*args)
23
+ args.each do |attr|
24
+ define_method("#{attr.to_s}=".to_sym) do |date|
25
+ date = (date.is_a?(String) ? DateTime.parse(date) : date) rescue date
26
+ instance_variable_set("@#{attr}", date)
27
+ end
28
+ end
29
+ end
30
+
31
+ def date_accessor(*args)
32
+ attr_reader(*args)
33
+ date_writer(*args)
34
+ end
35
+
36
+ def boolean_accessor(*attrs)
37
+ attr_accessor(*attrs)
38
+ alias_question(attrs)
39
+ end
40
+
41
+ def boolean_reader(*attrs)
42
+ attr_reader(*attrs)
43
+ alias_question(attrs)
44
+ end
45
+
46
+ private
47
+ def alias_question(attrs)
48
+ attrs.each{ |attr| define_method("#{attr}?"){ send(attr) || false } }
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,29 @@
1
+ module Mondo
2
+ class Response
3
+ attr_reader :response, :parsed
4
+ attr_accessor :error, :options
5
+
6
+ # Initializes a Response instance
7
+ #
8
+ # @param [Faraday::Response] response The Faraday response instance
9
+ def initialize(resp)
10
+ @response = resp
11
+ @parsed = -> { MultiJson.load(body) rescue body }.call
12
+ end
13
+
14
+ # The HTTP response headers
15
+ def headers
16
+ response.headers
17
+ end
18
+
19
+ # The HTTP response status code
20
+ def status
21
+ response.status
22
+ end
23
+
24
+ # The HTTP response body
25
+ def body
26
+ response.body || ''
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,80 @@
1
+ module Mondo
2
+ class Transaction < Resource
3
+
4
+ attr_accessor :id,
5
+ :description,
6
+ :notes,
7
+ :metadata,
8
+ :is_load,
9
+ :category,
10
+ :settled,
11
+ :decline_reason
12
+
13
+ date_accessor :created
14
+ date_accessor :settled
15
+
16
+ def declined?
17
+ raw_data['decline_reason'].present?
18
+ end
19
+
20
+ def amount
21
+ Money.new(raw_data['amount'], currency)
22
+ end
23
+
24
+ def local_amount
25
+ Money.new(raw_data['local_amount'], local_currency)
26
+ end
27
+
28
+ def account_balance
29
+ Money.new(raw_data['account_balance'], currency)
30
+ end
31
+
32
+ def currency
33
+ Money::Currency.new(raw_data['currency'])
34
+ end
35
+
36
+ def local_currency
37
+ Money::Currency.new(raw_data['local_currency'])
38
+ end
39
+
40
+ def save_metadata
41
+ self.client.api_patch("/transactions/#{self.id}", metadata: self.metadata)
42
+ end
43
+
44
+ def register_attachment(args={})
45
+ attachment = Attachment.new(
46
+ {
47
+ external_id: self.id,
48
+ file_url: args.fetch(:file_url),
49
+ file_type: args.fetch(:file_type)
50
+ },
51
+ self.client
52
+ )
53
+
54
+ self.attachments << attachment if attachment.register
55
+ end
56
+
57
+ def attachments
58
+ @transactions ||= begin
59
+ raw_data['attachments'].map { |tx| Attachment.new(tx, self.client) }
60
+ end
61
+ end
62
+
63
+ def merchant(opts={})
64
+ unless raw_data['merchant'].kind_of?(Hash)
65
+ # Go and refetch the transaction with merchant info expanded
66
+ self.raw_data['merchant'] = self.client.transaction(self.id, expand: [:merchant]).raw_data['merchant']
67
+ end
68
+
69
+ ::Mondo::Merchant.new(raw_data['merchant'], client) unless raw_data['merchant'].nil?
70
+ end
71
+
72
+ def tags
73
+ metadata["tags"]
74
+ end
75
+
76
+ def tags=(t)
77
+ metadata["tags"] = t
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,16 @@
1
+ require 'uri'
2
+
3
+ module Mondo
4
+ module Utils
5
+ extend self
6
+
7
+ # String Helpers
8
+ def camelize(str)
9
+ str.split('_').map(&:capitalize).join
10
+ end
11
+
12
+ def underscore(str)
13
+ str.gsub(/(.)([A-Z])/) { "#{$1}_#{$2.downcase}" }.downcase
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,3 @@
1
+ module Mondo
2
+ VERSION = '0.5.0'.freeze
3
+ end
@@ -0,0 +1,15 @@
1
+ module Mondo
2
+ class WebHook < Resource
3
+
4
+ attr_accessor :id, :account_id, :url
5
+
6
+ def save
7
+ self.client.api_post("webhooks", account_id: account_id, url: url)
8
+ end
9
+
10
+ def delete
11
+ self.client.api_delete("webhooks/#{self.id}")
12
+ true
13
+ end
14
+ end
15
+ end
data/lib/api/mondo.rb ADDED
@@ -0,0 +1,21 @@
1
+ module Mondo
2
+ require_relative '../api/mondo/client'
3
+ require_relative '../api/mondo/version'
4
+
5
+ require_relative '../api/mondo/response'
6
+ require_relative '../api/mondo/resource'
7
+ require_relative '../api/mondo/errors'
8
+ require_relative '../api/mondo/utils'
9
+
10
+ require_relative '../api/mondo/transaction'
11
+ require_relative '../api/mondo/feed_item'
12
+
13
+ require_relative '../api/mondo/card'
14
+ require_relative '../api/mondo/balance'
15
+ require_relative '../api/mondo/address'
16
+ require_relative '../api/mondo/transaction'
17
+ require_relative '../api/mondo/account'
18
+ require_relative '../api/mondo/merchant'
19
+ require_relative '../api/mondo/attachment'
20
+ require_relative '../api/mondo/web_hook'
21
+ end
@@ -0,0 +1,31 @@
1
+ require 'yaml'
2
+
3
+ class ConfigParser
4
+
5
+ def initialize(path_to_config)
6
+ @path_to_config = path_to_config
7
+ @parsed = nil
8
+
9
+ @parsed = begin
10
+ YAML.load(File.open(@path_to_config))
11
+ rescue ArgumentError => e
12
+ puts "Could not parse YAML: #{e.message}"
13
+ end if File.exist?(@path_to_config)
14
+
15
+ end
16
+
17
+ def parse
18
+ return nil unless valid?
19
+ @parsed
20
+ end
21
+
22
+ def valid?
23
+ return nil if @parsed.nil?
24
+ return nil if @parsed['access_token'].nil?
25
+ return nil if @parsed['account_id'].nil?
26
+ return nil if @parsed['user_id'].nil?
27
+
28
+ true
29
+ end
30
+
31
+ end
@@ -0,0 +1,27 @@
1
+ require_relative '../api/mondo'
2
+
3
+ class MonzoApi
4
+
5
+ def initialize(config)
6
+ @user_id = config.parse['user_id']
7
+ @account_id = config.parse['account_id']
8
+ @access_token = config.parse['access_token']
9
+
10
+ @monzo = Mondo::Client.new(
11
+ token: @access_token,
12
+ account_id: @user_id)
13
+ end
14
+
15
+ def balance
16
+ @monzo.balance @account_id
17
+ end
18
+
19
+ def accounts
20
+ @monzo.accounts
21
+ end
22
+
23
+ def transactions
24
+ @monzo.transactions
25
+ end
26
+
27
+ end
@@ -0,0 +1,3 @@
1
+ module Monzo
2
+ VERSION = '0.0.1'
3
+ end
data/lib/monzo.rb ADDED
@@ -0,0 +1,4 @@
1
+ require 'monzo/version.rb'
2
+
3
+ # Add requires for other files you add to your project here, so
4
+ # you just need to require this one file in your bin file
data/monzo.gemspec ADDED
@@ -0,0 +1,36 @@
1
+ # Ensure we require the local version and not one we might have installed already
2
+ require File.join([File.dirname(__FILE__), 'lib', 'monzo', 'version.rb'])
3
+ spec = Gem::Specification.new do |s|
4
+ s.name = 'monzo-cli'
5
+ s.version = Monzo::VERSION
6
+ s.author = 'cesar ferreira'
7
+ s.email = 'cesar.manuel.ferreira@gmail.com'
8
+ s.homepage = 'http://cesarferreira.com'
9
+ s.platform = Gem::Platform::RUBY
10
+ s.summary = 'A description of your project'
11
+ s.files = `git ls-files`.split("
12
+ ")
13
+ s.require_paths << 'lib'
14
+ s.required_ruby_version = '>= 2.0.0'
15
+ s.bindir = 'bin'
16
+ s.executables << 'monzo-cli'
17
+
18
+ s.add_development_dependency 'rake'
19
+ s.add_development_dependency 'aruba'
20
+ s.add_development_dependency 'rspec'
21
+ s.add_development_dependency 'pry'
22
+
23
+ s.add_runtime_dependency 'gli', '2.14.0'
24
+ s.add_runtime_dependency 'terminal-table', '>= 1.7.3'
25
+ # s.add_runtime_dependency 'mondo', '~> 0.5.0'
26
+ s.add_runtime_dependency 'colorize', '~> 0.7'
27
+
28
+
29
+
30
+ s.add_runtime_dependency 'oauth2', '~> 1.0'
31
+ s.add_runtime_dependency 'money'
32
+ s.add_runtime_dependency 'multi_json', '~> 1.10'
33
+ s.add_runtime_dependency 'activesupport', '~> 3.2'
34
+
35
+
36
+ end
@@ -0,0 +1,2 @@
1
+ user_id: user_18231092askdas9212
2
+ account_id: acc_0aksdaklsjSh28181
@@ -0,0 +1,3 @@
1
+ user_id: user_18231092askdas9212
2
+ account_id: acc_0aksdaklsjSh28181
3
+ access_token: Qnjdas8hakxdjasQscGVgnVGIVXpvpZ5uCxkQ5XLnDHnOPoBtXreQ6adBo
@@ -0,0 +1,48 @@
1
+ require 'spec_helper'
2
+ require 'monzo/config_parser'
3
+
4
+ describe '# Config Parser' do
5
+
6
+ it '# The file is present' do
7
+
8
+ # Given
9
+ path = 'spec/assets/test_config.yml'
10
+
11
+ # When
12
+ config = ConfigParser.new(path)
13
+
14
+ # Then
15
+ expect(config.parse).to_not be_nil
16
+ expect(config.parse['access_token']).to eq('Qnjdas8hakxdjasQscGVgnVGIVXpvpZ5uCxkQ5XLnDHnOPoBtXreQ6adBo')
17
+ expect(config.parse['account_id']).to eq('acc_0aksdaklsjSh28181')
18
+ expect(config.parse['user_id']).to eq('user_18231092askdas9212')
19
+
20
+ end
21
+
22
+ it '# The file is not present' do
23
+
24
+ # Given
25
+ path = 'spec/assets/NOT_CORRECT/test_config.yml'
26
+
27
+ # When
28
+ config = ConfigParser.new(path)
29
+
30
+ # Then
31
+ expect(config.parse).to be_nil
32
+
33
+ end
34
+
35
+ it '# The file is present but is corrupt' do
36
+
37
+ # Given
38
+ path = 'spec/assets/bad_test_config.yml'
39
+
40
+ # When
41
+ config = ConfigParser.new(path)
42
+
43
+ # Then
44
+ expect(config.parse).to be_nil
45
+
46
+ end
47
+
48
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+ require 'monzo/monzo_api'
3
+
4
+ describe '# Monzo API' do
5
+
6
+ # it '# The file is present' do
7
+ #
8
+ # # Given
9
+ # hash = 'spec/assets/test_config.yml'
10
+ #
11
+ # # When
12
+ # config = MonzoApi.new(hash)
13
+ #
14
+ # # Then
15
+ # expect(config.parse['access_token']).to eq('Qnjdas8hakxdjasQscGVgnVGIVXpvpZ5uCxkQ5XLnDHnOPoBtXreQ6adBo')
16
+ # expect(config.parse['account_id']).to eq('acc_0aksdaklsjSh28181')
17
+ # expect(config.parse['user_id']).to eq('user_18231092askdas9212')
18
+ #
19
+ # end
20
+ # expect { ... }.to raise_error
21
+
22
+ end
@@ -0,0 +1,25 @@
1
+ require 'monzo'
2
+ RSpec.configure do |config|
3
+ # rspec-expectations config goes here. You can use an alternate
4
+ # assertion/expectation library such as wrong or the stdlib/minitest
5
+ # assertions if you prefer.
6
+ config.expect_with :rspec do |expectations|
7
+ # This option will default to `true` in RSpec 4. It makes the `description`
8
+ # and `failure_message` of custom matchers include text for helper methods
9
+ # defined using `chain`, e.g.:
10
+ # be_bigger_than(2).and_smaller_than(4).description
11
+ # # => "be bigger than 2 and smaller than 4"
12
+ # ...rather than:
13
+ # # => "be bigger than 2"
14
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
15
+ end
16
+
17
+ # rspec-mocks config goes here. You can use an alternate test double
18
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
19
+ config.mock_with :rspec do |mocks|
20
+ # Prevents you from mocking or stubbing a method that does not exist on
21
+ # a real object. This is generally recommended, and will default to
22
+ # `true` in RSpec 4.
23
+ mocks.verify_partial_doubles = true
24
+ end
25
+ end