fakturan_nu 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +36 -0
- data/Gemfile +2 -0
- data/LICENSE +21 -0
- data/README.md +121 -0
- data/Rakefile +8 -0
- data/config/locales/en.yml +2 -0
- data/fakturan_nu.gemspec +26 -0
- data/lib/fakturan_nu/address.rb +5 -0
- data/lib/fakturan_nu/base.rb +88 -0
- data/lib/fakturan_nu/client.rb +19 -0
- data/lib/fakturan_nu/error.rb +69 -0
- data/lib/fakturan_nu/invoice.rb +40 -0
- data/lib/fakturan_nu/middleware/parse_json.rb +23 -0
- data/lib/fakturan_nu/middleware/raise_error.rb +33 -0
- data/lib/fakturan_nu/product.rb +7 -0
- data/lib/fakturan_nu/row.rb +7 -0
- data/lib/fakturan_nu/spyke_extensions.rb +24 -0
- data/lib/fakturan_nu.rb +87 -0
- data/test/basics_test.rb +26 -0
- data/test/exceptions_test.rb +104 -0
- data/test/models_test.rb +142 -0
- data/test/test_helper.rb +40 -0
- data/test/vcr_cassettes/models.yml +480 -0
- metadata +231 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f81bdfd634d37068875813a2c43c877ed859b4fb
|
4
|
+
data.tar.gz: 666f324bd953f0a55e9e6af4e8bbf70ad7405036
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c3591f8e8cb7cc5604029feec9e3a91b9e42a09fe302a97f8fe89aec1086abad8d30dafe9adee1163d330aeb7868210a3f9cc293af5ca9540b666f105e89194a
|
7
|
+
data.tar.gz: 4bd0783fff70fe74a037ee80f5a4e8df43873b6d4bb35e249775255d6cc42f789ec613386bd231d47625cd9be45c2d306d0c3d43f19b77b2c4c8d1c31738df59
|
data/.gitignore
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
/.config
|
4
|
+
/coverage/
|
5
|
+
/InstalledFiles
|
6
|
+
/pkg/
|
7
|
+
/spec/reports/
|
8
|
+
/test/tmp/
|
9
|
+
/test/version_tmp/
|
10
|
+
/tmp/
|
11
|
+
|
12
|
+
## Specific to RubyMotion:
|
13
|
+
.dat*
|
14
|
+
.repl_history
|
15
|
+
build/
|
16
|
+
|
17
|
+
## Documentation cache and generated files:
|
18
|
+
/.yardoc/
|
19
|
+
/_yardoc/
|
20
|
+
/doc/
|
21
|
+
/rdoc/
|
22
|
+
|
23
|
+
## Environment normalisation:
|
24
|
+
/.bundle/
|
25
|
+
/lib/bundler/man/
|
26
|
+
|
27
|
+
# for a library or gem, you might want to ignore these files since the code is
|
28
|
+
# intended to run in multiple environments; otherwise, check them in:
|
29
|
+
Gemfile.lock
|
30
|
+
.ruby-version
|
31
|
+
.ruby-gemset
|
32
|
+
|
33
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
34
|
+
.rvmrc
|
35
|
+
|
36
|
+
.DS_Store
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Imagineit AB
|
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
|
13
|
+
all 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
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
Ruby client for the fakturan.nu API
|
2
|
+
==============================
|
3
|
+
|
4
|
+
API client in ruby for the Swedish web based invoicing software [fakturan.nu](https://www.fakturan.nu). The API is currently in Beta and may be subject to change without notice until final release.
|
5
|
+
|
6
|
+
---
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
If you're using Rails/Bundler, add this to your Gemfile:
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
gem "fakturan_nu"
|
14
|
+
```
|
15
|
+
|
16
|
+
Then create an initializer in app/initializers called ``` fakturan_nu.rb ``` (or whatever name you choose) with the following:
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
Fakturan.setup 'your api username here', 'your api key/password here'
|
20
|
+
Fakturan.use_sandbox = true # Should be true during development/testing and false in production.
|
21
|
+
```
|
22
|
+
|
23
|
+
If you're not using Rails, just ``` require 'fakturan_nu'``` and run setup wherever you like.
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
### General
|
28
|
+
|
29
|
+
The client attempts to provide an interface similar to ActiveRecord.
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
Fakturan::Product.all
|
33
|
+
# GET "https://fakturan.nu/api/v2/products" and return an array of product objects
|
34
|
+
|
35
|
+
Fakturan::Product.find(1)
|
36
|
+
# GET "https://fakturan.nu/api/v2/products/1" and return a product object
|
37
|
+
|
38
|
+
product = Fakturan::Product.create(name: "Shoes")
|
39
|
+
# POST "https://fakturan.nu/api/v2/products" and return the saved product object
|
40
|
+
|
41
|
+
product = Fakturan::Product.new(name: "Shoes")
|
42
|
+
product.tax = 12
|
43
|
+
product.save
|
44
|
+
# POST "https://fakturan.nu/api/v2/products" and return the saved product object
|
45
|
+
|
46
|
+
product = Fakturan::Product.find(1)
|
47
|
+
# GET "https://fakturan.nu/api/v2/products/1" and return a product object
|
48
|
+
product.name = "Blue suede shoes" # Update a property
|
49
|
+
product.save
|
50
|
+
# PUT "https://fakturan.nu/api/v2/products/1" and return the updated product object
|
51
|
+
```
|
52
|
+
|
53
|
+
### Invoices
|
54
|
+
|
55
|
+
For creating invoices, a date and a client is required:
|
56
|
+
```ruby
|
57
|
+
invoice = Fakturan::Invoice.create(date: Date.today, client: { company: "Acme Inc" })
|
58
|
+
# POST "https://fakturan.nu/api/v2/invoices" # Will create a new client + invoice
|
59
|
+
|
60
|
+
invoice = Fakturan::Invoice.create(date: Date.today, client_id: 1)
|
61
|
+
# POST "https://fakturan.nu/api/v2/invoices" # Will create a new invoice for client with id: 1
|
62
|
+
|
63
|
+
```
|
64
|
+
|
65
|
+
### Available resources and properties
|
66
|
+
|
67
|
+
For a full list of resources and the properties of each type of resource, see the [api reference](https://sandbox.fakturan.nu/apidocs).
|
68
|
+
|
69
|
+
## Pagination
|
70
|
+
|
71
|
+
The Fakturan.nu API paginates results. When a collection is fetched, it contains pagination information on the metadata accessor. So in order to get all results, you can do:
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
products = Fakturan::Product.all # This will give you paginated results of 30 items per page by default
|
75
|
+
products.concat(Fakturan::Product.get(products.metadata[:next])) while products.metadata[:next]
|
76
|
+
```
|
77
|
+
|
78
|
+
You can change the default number of items per page (30) by passing in the per_page parameter:
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
products = Fakturan::Product.where(per_page: 100) # This will give you paginated results of 100 items per page
|
82
|
+
products.concat(Fakturan::Product.get(products.metadata[:next])) while products.metadata[:next]
|
83
|
+
```
|
84
|
+
|
85
|
+
The maximum number of items per page allowed is 100.
|
86
|
+
|
87
|
+
## Validation
|
88
|
+
|
89
|
+
Errors and validation are handled much like in ActiveRecord:
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
invoice = Fakturan::Invoice.new
|
93
|
+
invoice.save # false
|
94
|
+
invoice.errors.to_a # ["Client can't be blank", "Date can't be blank", "Date is invalid"]
|
95
|
+
```
|
96
|
+
|
97
|
+
As with ActiveRecord, the ``` save ``` and ``` create ``` methods will return ``` true ``` or ``` false ``` depending on validations, while ``` save! ``` and ``` create! ``` will raise an error if validations fail. The error that is raised in such cases is ``` Fakturan::Error::ResourceInvalid ```.
|
98
|
+
|
99
|
+
## Error handling
|
100
|
+
|
101
|
+
If the server responds with an error, or if the server cannot be reached, one of the following errors will be raised:
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
Fakturan::Error::AccessDenied # http status code: 401
|
105
|
+
Fakturan::Error::ResourceNotFound # http status code: 404
|
106
|
+
Fakturan::Error::ConnectionFailed # http status code: 407
|
107
|
+
Fakturan::Error::ResourceInvalid # http status code: 422
|
108
|
+
Fakturan::Error::ClientError # http status code: other in the 400-499 range
|
109
|
+
Fakturan::Error::ServerError # http status code: other in the 500-599 range
|
110
|
+
```
|
111
|
+
|
112
|
+
These errors are all descendents of ```Fakturan::Error``` and should be caught in a begin-rescue block like so (example in a Rails-controller):
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
begin
|
116
|
+
invoice = Fakturan::Invoice.create!
|
117
|
+
rescue Fakturan::Error => error
|
118
|
+
render plain: error.message, status: error.status
|
119
|
+
end
|
120
|
+
```
|
121
|
+
|
data/Rakefile
ADDED
data/fakturan_nu.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
Gem::Specification.new do |s|
|
3
|
+
s.name = 'fakturan_nu'
|
4
|
+
s.version = '1.0.0'
|
5
|
+
s.date = '2014-12-17'
|
6
|
+
s.summary = 'A ruby client for the Fakturan.nu - API'
|
7
|
+
s.description = 'A ruby client for the Fakturan.nu - API. Fakturan.nu is a webbapp for billing.'
|
8
|
+
s.authors = ['Jonathan Bourque Olivegren']
|
9
|
+
s.email = 'jonathan@imagineit.se'
|
10
|
+
s.files = `git ls-files`.split("\n") # http://www.codinginthecrease.com/news_article/show/350843?referrer_id=948927
|
11
|
+
s.homepage = 'http://rubygems.org/gems/fakturan_nu'
|
12
|
+
s.license = 'MIT'
|
13
|
+
|
14
|
+
s.add_dependency 'spyke', '~> 1.8', '>= 1.8.7'
|
15
|
+
s.add_dependency 'faraday', '>= 0.8', '< 1.0'
|
16
|
+
s.add_dependency 'multi_json', '~> 1.11', '>= 1.11.0'
|
17
|
+
# So we can use model.errors.details before Rails 5.
|
18
|
+
s.add_dependency 'active_model-errors_details', '~> 1.1', '>= 1.1.1'
|
19
|
+
|
20
|
+
s.add_development_dependency 'rake'
|
21
|
+
s.add_development_dependency 'webmock'
|
22
|
+
s.add_development_dependency 'minitest'
|
23
|
+
s.add_development_dependency 'minitest-reporters'
|
24
|
+
s.add_development_dependency 'minitest-around'
|
25
|
+
s.add_development_dependency 'vcr'
|
26
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'fakturan_nu/error'
|
2
|
+
|
3
|
+
module Fakturan
|
4
|
+
class Base < Spyke::Base
|
5
|
+
include_root_in_json false
|
6
|
+
|
7
|
+
def self.connection
|
8
|
+
Fakturan.connection
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.create!(attrs = {})
|
12
|
+
instance = self.new(attrs)
|
13
|
+
instance.save!
|
14
|
+
return instance
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.create(attrs = {})
|
18
|
+
instance = self.new(attrs)
|
19
|
+
instance.save
|
20
|
+
return instance
|
21
|
+
end
|
22
|
+
|
23
|
+
def save
|
24
|
+
errors.clear
|
25
|
+
super
|
26
|
+
return !errors.any?
|
27
|
+
end
|
28
|
+
|
29
|
+
def save!
|
30
|
+
raise Fakturan::Error::ResourceInvalid, self unless self.save
|
31
|
+
end
|
32
|
+
|
33
|
+
# This is an override from: https://github.com/balvig/spyke/blob/master/lib/spyke/http.rb
|
34
|
+
# to allow for nested errors on associated objects
|
35
|
+
def add_errors_to_model(errors_hash)
|
36
|
+
errors_hash.each do |field, field_errors|
|
37
|
+
ass_name, field_name = field.split(".").map(&:to_sym)
|
38
|
+
field_name = ass_name unless field_name
|
39
|
+
|
40
|
+
if association = self.class.reflect_on_association(ass_name) # The errors are for an associatied object
|
41
|
+
if association.type == Spyke::Associations::HasMany
|
42
|
+
field_errors.each do |new_error_hash_with_index|
|
43
|
+
new_error_hash_with_index.each do |index, inner_errors_hash|
|
44
|
+
error_attribute = inner_errors_hash.keys.first.split('.').last.to_sym
|
45
|
+
self.send(ass_name)[index.to_i].add_to_errors(error_attribute, inner_errors_hash.values.last)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
else # It's a belongs_to or has_one
|
49
|
+
# We add the error to the associated object
|
50
|
+
self.send(ass_name).add_to_errors(field_name, field_errors)
|
51
|
+
# and then we get the errors (with generated messages) and add them to
|
52
|
+
# the parent (but without details, like nested_attributes works)
|
53
|
+
# This only makes sense on belongs_to and has_one, since it's impossible
|
54
|
+
# to know which object is refered to on has_many
|
55
|
+
self.send(ass_name).errors.each do |attribute, message|
|
56
|
+
attribute = "#{ass_name}.#{attribute}"
|
57
|
+
errors[attribute] << message
|
58
|
+
errors[attribute].uniq!
|
59
|
+
end
|
60
|
+
end
|
61
|
+
else
|
62
|
+
self.add_to_errors(field_name.to_sym, field_errors)
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def add_to_errors field_name, field_errors
|
69
|
+
field_errors.each do |attributes|
|
70
|
+
attributes = attributes.symbolize_keys
|
71
|
+
error_name = attributes.delete(:error).to_sym
|
72
|
+
errors.add(field_name.to_sym, error_name, attributes)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# This is an override from: https://github.com/balvig/spyke/blob/master/lib/spyke/http.rb
|
77
|
+
# In order to re-raise Faraday error as Fakturan error
|
78
|
+
def self.request(method, path, params = {})
|
79
|
+
begin
|
80
|
+
super
|
81
|
+
rescue Faraday::ConnectionFailed => e
|
82
|
+
raise Fakturan::Error::ConnectionFailed, e.message
|
83
|
+
rescue Faraday::TimeoutError => e
|
84
|
+
raise Fakturan::Error::ConnectionFailed, e.message
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Fakturan
|
2
|
+
class Client < Base
|
3
|
+
uri "clients/(:id)"
|
4
|
+
has_many :invoices, uri: "clients/:id/invoices", foreign_key: 'id'
|
5
|
+
has_one :address
|
6
|
+
|
7
|
+
accepts_nested_attributes_for :address
|
8
|
+
|
9
|
+
attributes :number, :first_name, :last_name, :email, :company, :phone, :home_phone, :mobile_phone, :fax, :org_number, :private, :web, :vat_number
|
10
|
+
|
11
|
+
def address=(attrs_or_obj)
|
12
|
+
if attrs_or_obj.respond_to?(:each)
|
13
|
+
send(:address_attributes=, attrs_or_obj)
|
14
|
+
else
|
15
|
+
super
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'active_model/errors_details'
|
2
|
+
|
3
|
+
# This is just to provide a set of errors with consistent scope
|
4
|
+
# https://github.com/lostisland/faraday/blob/master/lib/faraday/error.rb
|
5
|
+
module Fakturan
|
6
|
+
class Error < StandardError; end
|
7
|
+
|
8
|
+
class WithResponse < Error
|
9
|
+
attr_reader :response
|
10
|
+
|
11
|
+
def status
|
12
|
+
response[:status]
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(ex, response = nil)
|
16
|
+
@wrapped_exception = nil
|
17
|
+
@response = response
|
18
|
+
|
19
|
+
if ex.respond_to?(:backtrace) # If ex behaves like an Exception
|
20
|
+
super(ex.message)
|
21
|
+
@wrapped_exception = ex
|
22
|
+
elsif ex.respond_to?(:each_key) # If ex behaves like a Hash
|
23
|
+
super("the server responded with status #{ex[:status]} - #{ex[:body]}")
|
24
|
+
@response = ex
|
25
|
+
else
|
26
|
+
super(ex.to_s)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def backtrace
|
31
|
+
if @wrapped_exception
|
32
|
+
@wrapped_exception.backtrace
|
33
|
+
else
|
34
|
+
super
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def inspect
|
39
|
+
%(#<#{self.class}>)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class ClientError < WithResponse; end
|
44
|
+
class ServerError < WithResponse; end
|
45
|
+
class AccessDenied < WithResponse; end
|
46
|
+
class ResourceNotFound < WithResponse; end
|
47
|
+
class ParseError < WithResponse; end
|
48
|
+
|
49
|
+
class ConnectionFailed < Error
|
50
|
+
def status; 407; end
|
51
|
+
end
|
52
|
+
|
53
|
+
class ResourceInvalid < Error
|
54
|
+
attr_reader :model
|
55
|
+
|
56
|
+
def status; 422; end
|
57
|
+
|
58
|
+
def initialize(model)
|
59
|
+
@model = model
|
60
|
+
msg = @model.errors.details #to_a.join(" ")
|
61
|
+
super(msg)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# This is just so that we can have Fakturan::Error be both a class and a "scope": Fakturan::Error::ResourceInvalid
|
66
|
+
[:WithResponse, :ClientError, :ServerError, :AccessDenied, :ResourceNotFound, :ParseError, :ConnectionFailed, :ResourceInvalid].each do |const|
|
67
|
+
Error.const_set(const, Fakturan.const_get(const))
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Fakturan
|
2
|
+
class Invoice < Base
|
3
|
+
uri "invoices/(:id)"
|
4
|
+
|
5
|
+
# The foreign_key decides which attribute will be used to set the id
|
6
|
+
belongs_to :client, uri: "clients/:id", foreign_key: 'client_id'
|
7
|
+
has_one :address, uri: nil
|
8
|
+
|
9
|
+
has_many :rows, uri: nil
|
10
|
+
|
11
|
+
accepts_nested_attributes_for :rows, :address, :client
|
12
|
+
|
13
|
+
# These create getter methods on new instances, mostly for use in forms
|
14
|
+
attributes :number, :date, :client_id, :days, :our_reference, :your_reference, :sent, :paid, :paid_at, :interval_period, :auto_send, :recurring, :last_day_of_month, :start_recurring_from, :locale, :currency
|
15
|
+
|
16
|
+
def rows=(attrs_or_obj_array)
|
17
|
+
if attrs_or_obj_array.first.respond_to?(:each)
|
18
|
+
send(:rows_attributes=, attrs_or_obj_array)
|
19
|
+
else
|
20
|
+
super
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def address=(attrs_or_obj)
|
25
|
+
if attrs_or_obj.respond_to?(:each)
|
26
|
+
send(:address_attributes=, attrs_or_obj)
|
27
|
+
else
|
28
|
+
super
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def client=(attrs_or_obj)
|
33
|
+
if attrs_or_obj.respond_to?(:each)
|
34
|
+
send(:client_attributes=, attrs_or_obj)
|
35
|
+
else
|
36
|
+
super
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Fakturan
|
2
|
+
module Response
|
3
|
+
class ParseJSON < Faraday::Response::Middleware
|
4
|
+
def parse(body)
|
5
|
+
json = MultiJson.load(body, symbolize_keys: true)
|
6
|
+
res = {
|
7
|
+
data: json[:data],
|
8
|
+
metadata: json[:paging],
|
9
|
+
errors: json[:errors]
|
10
|
+
}
|
11
|
+
return res
|
12
|
+
end
|
13
|
+
|
14
|
+
def on_complete(env)
|
15
|
+
begin # https://github.com/lostisland/faraday/blob/master/lib/faraday/response.rb
|
16
|
+
env.body = parse(env.body) if env.parse_body?
|
17
|
+
rescue MultiJson::ParseError => exception
|
18
|
+
raise Fakturan::Error::ParseError, {:status => env.status, :headers => env.response_headers, :body => env.body}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'fakturan_nu/error'
|
2
|
+
|
3
|
+
module Fakturan
|
4
|
+
module Response
|
5
|
+
class RaiseError < Faraday::Response::Middleware
|
6
|
+
ClientErrorStatuses = 400...499
|
7
|
+
ServerErrorStatuses = 500...599
|
8
|
+
|
9
|
+
def on_complete(env)
|
10
|
+
case env[:status]
|
11
|
+
when 401
|
12
|
+
raise Fakturan::Error::AccessDenied, response_values(env)
|
13
|
+
when 404
|
14
|
+
raise Fakturan::Error::ResourceNotFound, response_values(env)
|
15
|
+
when 407
|
16
|
+
# mimic the behavior that we get with proxy requests with HTTPS
|
17
|
+
raise Faraday::ConnectionFailed, %{407 "Proxy Authentication Required "} # We raise this instead of our own ConnectionFailed error, since Faraday::ConnectionFailed will be raise on timeouts or refused connections anyway
|
18
|
+
when 422
|
19
|
+
# We don't do anything except fallback to standard behaviour in Spyke, which is to store errors on .errors and not raise an exception
|
20
|
+
# If an exception is desirable, then use .save! on the model instead of .save.
|
21
|
+
when ClientErrorStatuses
|
22
|
+
raise Fakturan::Error::ClientError, response_values(env)
|
23
|
+
when ServerErrorStatuses
|
24
|
+
raise Fakturan::Error::ServerError, response_values(env)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def response_values(env)
|
29
|
+
{:status => env.status, :headers => env.response_headers, :body => env.body}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Spyke
|
2
|
+
class Collection
|
3
|
+
# For pagination.
|
4
|
+
# Makes it possible to do ps = Fakturan::Product.all and then
|
5
|
+
# ps.concat(Fakturan::Product.get(ps.metadata[:next])) while ps.metadata[:next])
|
6
|
+
def concat ary_or_collection
|
7
|
+
@metadata = ary_or_collection.metadata if ary_or_collection.respond_to?(:metadata)
|
8
|
+
super
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class Relation
|
13
|
+
# For pagination and more array-like behaviour.
|
14
|
+
# It's very tempting to add << as well, but as I recall, it didn't behave as expected
|
15
|
+
delegate :concat, :+, :-, to: :find_some
|
16
|
+
end
|
17
|
+
|
18
|
+
module Associations
|
19
|
+
class Builder
|
20
|
+
# Allows us to do reflect_on_association(ass_name).type
|
21
|
+
attr_accessor :type
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/fakturan_nu.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require 'spyke'
|
3
|
+
#require 'active_support/json/decoding' # if we want date-strings to be typecast as DateTimes. Can be done through Faraday-middleware too.
|
4
|
+
require 'faraday_middleware'
|
5
|
+
require 'multi_json'
|
6
|
+
require 'fakturan_nu/spyke_extensions'
|
7
|
+
require 'fakturan_nu/middleware/parse_json'
|
8
|
+
require 'fakturan_nu/middleware/raise_error'
|
9
|
+
|
10
|
+
require 'fakturan_nu/base'
|
11
|
+
require 'fakturan_nu/address'
|
12
|
+
require 'fakturan_nu/product'
|
13
|
+
require 'fakturan_nu/client'
|
14
|
+
require 'fakturan_nu/row'
|
15
|
+
require 'fakturan_nu/invoice'
|
16
|
+
|
17
|
+
I18n.load_path += Dir.glob( File.dirname(__FILE__) + "lib/locales/*.{rb,yml}" )
|
18
|
+
|
19
|
+
module Fakturan
|
20
|
+
@use_sandbox = false
|
21
|
+
@api_version = 2
|
22
|
+
|
23
|
+
mattr_accessor :accept_language
|
24
|
+
|
25
|
+
def self.setup username = nil, pass = nil
|
26
|
+
#self.parse_json_times = true
|
27
|
+
|
28
|
+
@username, @pass = username, pass
|
29
|
+
self.accept_language = 'en-US'
|
30
|
+
|
31
|
+
@connection = Faraday.new(url: build_url) do |connection|
|
32
|
+
# Request
|
33
|
+
connection.request :json
|
34
|
+
connection.use Faraday::Request::BasicAuthentication, @username, @pass
|
35
|
+
|
36
|
+
# Response
|
37
|
+
connection.use Fakturan::Response::ParseJSON
|
38
|
+
connection.use Fakturan::Response::RaiseError
|
39
|
+
|
40
|
+
# Adapter should be last
|
41
|
+
connection.adapter Faraday.default_adapter
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.connection
|
46
|
+
@connection
|
47
|
+
end
|
48
|
+
|
49
|
+
#def self.parse_json_times=(true_or_false)
|
50
|
+
# ActiveSupport.parse_json_times = true_or_false
|
51
|
+
#end
|
52
|
+
|
53
|
+
def self.use_token_auth(token)
|
54
|
+
@connection.token_auth(token)
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.use_basic_auth(username = nil, pass = nil)
|
58
|
+
@username, @pass = username, pass if (username && pass)
|
59
|
+
@connection.basic_auth(@username, @pass)
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.use_sandbox=(true_or_false)
|
63
|
+
@use_sandbox = true_or_false
|
64
|
+
rebuild_url
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.api_version=(version_no)
|
68
|
+
@api_version = version_no
|
69
|
+
rebuild_url
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.url
|
73
|
+
@connection.url_prefix.to_s
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.url=(new_url)
|
77
|
+
@connection.url_prefix = new_url
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.build_url
|
81
|
+
"https://#{@use_sandbox ? 'sandbox.' : ''}fakturan.nu/api/v#{@api_version}"
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.rebuild_url
|
85
|
+
self.url = self.build_url
|
86
|
+
end
|
87
|
+
end
|
data/test/basics_test.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
VCR.turn_off!
|
3
|
+
|
4
|
+
module Fakturan
|
5
|
+
class BasicsTest < MiniTest::Test
|
6
|
+
|
7
|
+
# They are not, but I dislike them jumping around in the output. Makes it harder to compare two test runs.
|
8
|
+
i_suck_and_my_tests_are_order_dependent!
|
9
|
+
|
10
|
+
def test_auth_with_token_and_back_to_basic
|
11
|
+
basic_auth_endpoint = stub_api_request(:get, '/clients/1').to_return(body: '{"data":{"id": 1, "name":"DCT"}}', status: 200)
|
12
|
+
|
13
|
+
token_endpoint = stub_request(:get, "http://#{BASE_URL}/clients/1")
|
14
|
+
.with(headers: { authorization: "Token token=\"XYZ\""})
|
15
|
+
.to_return(body: '{"data":{"id": 1, "name":"DCT"}}', status: 200)
|
16
|
+
|
17
|
+
Fakturan::Client.find(1)
|
18
|
+
Fakturan.use_token_auth('XYZ')
|
19
|
+
Fakturan::Client.find(1)
|
20
|
+
Fakturan.use_basic_auth
|
21
|
+
Fakturan::Client.find(1)
|
22
|
+
assert_requested basic_auth_endpoint, times: 2
|
23
|
+
assert_requested token_endpoint, times: 1
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|