diff --git a/README.md b/README.md index 3ec2827..8aec5cc 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,20 @@ movements = account.get_movements # Or get all the movements since a specific date movements = account.get_movements(since: '2020-08-15') +``` +## Quickstart with the new refactored API +```ruby +require 'fintoc' + +Fintoc.api_key = 'sk_test_9c8d8CeyBTx1VcJzuDgpm4H-bywJCeSx' +link = Fintoc::Link.find('6n12zLmai3lLE9Dq_token_gvEJi8FrBge4fb3cz7Wp856W') +account = link.accounts.find(type: 'checking_account') + +# Get the las 30 movements +movements = account.movements + +# Or get all the movements since a specific date +movements = account.movements.find(since: '2020-08-15') ``` And that’s it! @@ -86,6 +100,22 @@ link.show_accounts ``` +### Get accounts with the new refactored API + +```ruby +require 'fintoc' + +client = Fintoc.api_key = 'api_key' +link = Fintoc::Link.find('link_token') +puts link.accounts + +# Or... you can pretty print all the accounts in a Link + +link = Fintoc::Link.find('link_token') +link.show_accounts + +``` + If you want to find a specific account in a link, you can use **find**. You can search by any account field: ```ruby diff --git a/lib/fintoc.rb b/lib/fintoc.rb index 022b5c8..e3ea734 100644 --- a/lib/fintoc.rb +++ b/lib/fintoc.rb @@ -2,5 +2,20 @@ require 'fintoc/errors' require 'fintoc/client' +#Name API resources +require 'fintoc/resources' + +#Support classes +require 'fintoc/resource' + + +#API Operations +require 'fintoc/api_operations/request' +require 'fintoc/api_operations/delete' + + module Fintoc + class << self + attr_accessor :api_key + end end diff --git a/lib/fintoc/api_operations/delete.rb b/lib/fintoc/api_operations/delete.rb new file mode 100644 index 0000000..d09c76c --- /dev/null +++ b/lib/fintoc/api_operations/delete.rb @@ -0,0 +1,9 @@ +module Fintoc + module APIOperations + module Delete + def delete + request(:delete, resource_url) + end + end + end +end diff --git a/lib/fintoc/api_operations/request.rb b/lib/fintoc/api_operations/request.rb new file mode 100644 index 0000000..d594920 --- /dev/null +++ b/lib/fintoc/api_operations/request.rb @@ -0,0 +1,45 @@ +module Fintoc + module APIOperations + module Request + def self.included(base) + base.extend(ClassMethods) + end + + module ClassMethods + def request(method, url, params={}) + check_api_key! + raise "Not supported method" unless %i[get delete].include(method) + api_client.send(method).call(url, **params) + end + + def fetch_next(method, url, params={}) + first = request(method, url, params) + return first if params.empty? + + first + Utils.flatten(api_client.fetch_next) + end + + def api_client + @api_client = Client.new(Fintoc.api_key) + end + + protected def check_api_key! + return if Fintoc.api_key + + raise AuthenticationError, <<~MSG + No API key provided. + Set your API key using 'Fitoc.api_key = ' + MSG + end + end + + protected def request(method, url, params = {}) + self.class.request(method, url, params) + end + + protected def fetch_next(method, url, params = {}) + self.class.fetch_next(method, url, params) + end + end + end +end diff --git a/lib/fintoc/client.rb b/lib/fintoc/client.rb index 465e36d..151b4ca 100644 --- a/lib/fintoc/client.rb +++ b/lib/fintoc/client.rb @@ -14,7 +14,6 @@ def initialize(api_key) @user_agent = "fintoc-ruby/#{Fintoc::VERSION}" @headers = { "Authorization": @api_key, "User-Agent": @user_agent } @link_headers = nil - @link_header_pattern = '<(?.*)>;\s*rel="(?.*)"' @default_params = {} end @@ -82,7 +81,7 @@ def client end def parse_headers(dict, link) - matches = link.strip.match(@link_header_pattern) + matches = link.strip.match(Fintoc::Constants::PAGINATION_REGEX) dict[matches[:rel]] = matches[:url] dict end diff --git a/lib/fintoc/constants.rb b/lib/fintoc/constants.rb index 11d2b2e..585b8ec 100644 --- a/lib/fintoc/constants.rb +++ b/lib/fintoc/constants.rb @@ -4,5 +4,6 @@ module Constants GENERAL_DOC_URL = "https://fintoc.com/docs" SCHEME = "https://" BASE_URL = "api.fintoc.com/v1/" + PAGINATION_REGEX = '<(?.*)>;\s*rel="(?.*)"' end end diff --git a/lib/fintoc/resource.rb b/lib/fintoc/resource.rb new file mode 100644 index 0000000..8ae6a96 --- /dev/null +++ b/lib/fintoc/resource.rb @@ -0,0 +1,105 @@ +module Fintoc + class Resource + include Fintoc::APIOperations::Request + include Fintoc::APIOperations::Delete + + class << self + attr_reader :resource_url + + def class_name + name.split("::")[-1] + end + + def resource_url + if self == Resource + raise NotImplementedError, <<~MSG + Fintoc::Resource is an abstract class. You should perform actions + on its subsclasses (Link, Account, Movement, etc) + MSG + end + + resource_url || "#{class_name.downcase}" + end + + def find(id) + response = request(:get, "#{resource_url}/#{id}") + new(response) + end + + def all(params = {}) + response = request(:get, resource_url, params) + response.map {|data| new(data)} + end + + def belongs_to(resource_name, args={}) + class_name = args[:class_name] + unless resource_name && class_name + raise <<~MSG + You must specify the resource name and its class name + for example has_many :accounts, class_name: 'Account' + MSG + end + class_name = "::Fintoc::#{class_name}" unless class_name.include?("Fintoc") + if resource == :method + define_method(resource_name) do |params = {}| + data = request(:get, resource_url, params) + Object.const_get(class_name).new(**data) + end + end + + def has_many(resource_name, args={}) + class_name = args[:class_name] + unless resource_name && class_name + raise <<~MSG + You must specify the resource name and its class name + for example has_many :accounts, class_name: 'Account' + MSG + end + + # Add namespace to class_name + class_name = "::Fintoc::#{class_name}" unless class_name.include?("Fintoc") + define_method(resource_name) do |params = {}| + #link/accounts + fetch_next( + :get, + "#{resource_url}/#{resource_name}", params + ).lazy.map do |data| + Object.const_get(class_name).new(**data) + end + end + end + end + + def initialize(data={}) + @data = @unsaved_data = {} + initialize_from_data(data) + end + + def initilize_from_data(data) + klass = self.class + + @data = data + + data.each_key do |k| + klass.define_method(k) { @data[k]} + + klass.define_method(:"#{k}=") do |value| + @data[k] = @unsaved_data[k] = value + end + + next unless[FalseClass, TrueClass].include?( + @data[k] + ) + + klass.define_method(:"#{k}?") do + @data[k] + end + end + self + end + + def resource_url + "#{self.resource_url}/#{id}" + end + end +end diff --git a/lib/fintoc/resources.rb b/lib/fintoc/resources.rb new file mode 100644 index 0000000..1dcab09 --- /dev/null +++ b/lib/fintoc/resources.rb @@ -0,0 +1,7 @@ + +require 'fintoc/resources/account' +require 'fintoc/resources/link' +require 'fintoc/resources/institution' +require 'fintoc/resources/balance' +require 'fintoc/resources/movement' +require 'fintoc/resources/transfer_account' diff --git a/lib/fintoc/resources/account.rb b/lib/fintoc/resources/account.rb index 2946145..cdce669 100644 --- a/lib/fintoc/resources/account.rb +++ b/lib/fintoc/resources/account.rb @@ -4,58 +4,18 @@ require 'fintoc/resources/balance' module Fintoc - class Account + class Account < Resource include Utils - attr_reader :id, :name, :holder_name, :currency, :type, :refreshed_at, - :official_name, :number, :holder_id, :balance, :movements - def initialize( - id:, - name:, - official_name:, - number:, - holder_id:, - holder_name:, - type:, - currency:, - refreshed_at: nil, - balance: nil, - movements: nil, - client: nil, - ** - ) - @id = id - @name = name - @official_name = official_name - @number = number - @holder_id = holder_id - @holder_name = holder_name - @type = type - @currency = currency - @refreshed_at = DateTime.iso8601(refreshed_at) if refreshed_at - @balance = Fintoc::Balance.new(**balance) - @movements = movements || [] - @client = client - end - - def update_balance - @balance = Fintoc::Balance.new(**get_account[:balance]) - end - def get_movements(**params) - _get_movements(**params).lazy.map { |movement| Fintoc::Movement.new(**movement) } - end - - def update_movements(**params) - @movements += get_movements(**params).to_a - @movements = @movements.uniq.sort_by(&:post_date) - end + belongs_to :balance, class_name: Fintoc::Balance.to_s + has_many :movements, class_name: Fintoc::Movement.to_s def show_movements(rows = 5) - puts("This account has #{Utils.pluralize(@movements.size, 'movement')}.") + puts("This account has #{Utils.pluralize(movements.size, 'movement')}.") - return unless @movements.any? + return unless movements.any? - movements = @movements.to_a.slice(0, rows) + movements = movements.to_a.slice(0, rows) .map.with_index do |mov, index| [index + 1, mov.amount, mov.currency, mov.description, mov.locale_date] end @@ -65,20 +25,7 @@ def show_movements(rows = 5) end def to_s - "πŸ’° #{@holder_name}’s #{@name} #{@balance}" - end - - private - - def get_account - @client.get.call("accounts/#{@id}") - end - - def _get_movements(**params) - first = @client.get.call("accounts/#{@id}/movements", **params) - return first if params.empty? - - first + Utils.flatten(@client.fetch_next) + "πŸ’° #{holder_name}’s #{@name} #{@balance}" end end end diff --git a/lib/fintoc/resources/link.rb b/lib/fintoc/resources/link.rb index f1611cc..7af1339 100644 --- a/lib/fintoc/resources/link.rb +++ b/lib/fintoc/resources/link.rb @@ -1,82 +1,41 @@ require 'date' require 'tabulate' require 'fintoc/utils' -require 'fintoc/resources/account' -require 'fintoc/resources/institution' require 'tabulate' module Fintoc - class Link - attr_reader :id, :username, :holder_type, :institution, :created_at, :mode, - :accounts, :link_token - include Utils - def initialize( - id:, - username:, - holder_type:, - institution:, - created_at:, - mode:, - accounts: nil, - link_token: nil, - client: nil, - ** - ) - @id = id - @username = username - @holder_type = holder_type - @institution = Fintoc::Institution.new(**institution) - @created_at = Date.iso8601(created_at) - @mode = mode - @accounts = if accounts.nil? - [] - else - accounts.map { |data| Fintoc::Account.new(**data, client: client) } - end - @token = link_token - @client = client - end + class Link < Resource - def find_all(**kwargs) - raise 'You must provide *exactly one* account field.' if kwargs.size != 1 + has_many :accounts, class_name: Account.to_s - field, value = kwargs.to_a.first - @accounts.select do |account| - account.send(field.to_sym) == value - end - end + belongs_to :institution, class_name: Institution.to_s - def find(**kwargs) - results = find_all(**kwargs) - results.any? ? results.first : nil - end + include Utils def show_accounts(rows = 5) - puts "This links has #{Utils.pluralize(@accounts.size, 'account')}" + puts "This links has #{Utils.pluralize(accounts.size, 'account')}" return unless @accounts.any? - accounts = @accounts.to_a.slice(0, rows) - .map.with_index do |acc, index| - [index + 1, acc.name, acc.holder_name, acc.currency] - end + accounts = accounts + .to_a + .slice(0, rows) + .map.with_index do |acc, index| + [index + 1, acc.name, acc.holder_name, acc.currency] + end headers = ['#', 'Name', 'Holder', 'Currency'] puts puts tabulate(headers, accounts, indent: 4, style: 'fancy') end def update_accounts - @accounts.each do |account| + accounts.each do |account| account.update_balance account.update_movements end end - def delete - @client.delete_link(@id) - end - def to_s - "<#{@username}@#{@institution.name}> πŸ”— " + "<#{username}@#{institution.name}> πŸ”— " end end end diff --git a/lib/fintoc/utils.rb b/lib/fintoc/utils.rb index 9e1cf77..aac93f5 100644 --- a/lib/fintoc/utils.rb +++ b/lib/fintoc/utils.rb @@ -32,7 +32,7 @@ def pick(dict_, key) # @param suffix [String] # @return [String] def pluralize(amount, noun, suffix = 's') - quantifier = amount or 'no' + quantifier = amount || 'no' "#{quantifier} #{amount == 1 ? noun : noun + suffix}" end diff --git a/lib/fintoc/version.rb b/lib/fintoc/version.rb index fca5c30..f67bc09 100644 --- a/lib/fintoc/version.rb +++ b/lib/fintoc/version.rb @@ -1,3 +1,3 @@ module Fintoc - VERSION = "0.1.0" + VERSION = "0.2.0" end