badbill 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,34 @@
1
+ # How to contribute
2
+
3
+ This API client is implemented for my own use, so I will only work on the parts I need for myself.
4
+ Additional improvements, comments on code and implementation and bug fixes are very welcome.
5
+
6
+ ## Report bugs, improvements or feature requests
7
+
8
+ * Submit a ticket for your issue, assuming one does not already exist.
9
+ * Clearly describe the issue including steps to reproduce when it is a bug.
10
+ * Make sure you fill in the earliest version that you know has the issue.
11
+
12
+ ## Making Changes
13
+
14
+ * Fork the repository on GitHub.
15
+ * Create a topic branch from where you want to base your work.
16
+ * Make commits of logical units.
17
+ * Check for unnecessary whitespace with `git diff --check` before committing.
18
+ * Make sure your commit messages are in the proper format.
19
+ * Run _all_ the tests to assure nothing else was accidentally broken.
20
+ * Include tests for new features and bug fixes (if you don't know how to do that, feel free to ask, I will help).
21
+
22
+
23
+ ## Submitting Changes
24
+
25
+ * Push your changes to a topic branch in your fork of the repository.
26
+ * Submit a pull request to the repository.
27
+ * Wait for my response.
28
+
29
+ # Contact
30
+
31
+ Feel free to contact me with any issues or questions.
32
+
33
+ * Mail: badboy@archlinux.us
34
+ * Twitter: [@badboy_](https://twitter.com/badboy_)
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ group :development do
4
+ gem 'yard'
5
+ gem 'redcarpet'
6
+ gem 'simplecov'
7
+ end
8
+
9
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,51 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ badbill (0.0.1)
5
+ faraday
6
+ faraday_middleware
7
+ hashie
8
+ yajl-ruby
9
+
10
+ GEM
11
+ remote: https://rubygems.org/
12
+ specs:
13
+ addressable (2.3.2)
14
+ crack (0.3.1)
15
+ diff-lcs (1.1.3)
16
+ faraday (0.8.4)
17
+ multipart-post (~> 1.1)
18
+ faraday_middleware (0.8.8)
19
+ faraday (>= 0.7.4, < 0.9)
20
+ hashie (1.2.0)
21
+ multi_json (1.3.6)
22
+ multipart-post (1.1.5)
23
+ redcarpet (2.1.1)
24
+ rspec (2.11.0)
25
+ rspec-core (~> 2.11.0)
26
+ rspec-expectations (~> 2.11.0)
27
+ rspec-mocks (~> 2.11.0)
28
+ rspec-core (2.11.1)
29
+ rspec-expectations (2.11.3)
30
+ diff-lcs (~> 1.1.3)
31
+ rspec-mocks (2.11.3)
32
+ simplecov (0.6.4)
33
+ multi_json (~> 1.0)
34
+ simplecov-html (~> 0.5.3)
35
+ simplecov-html (0.5.3)
36
+ webmock (1.8.10)
37
+ addressable (>= 2.2.7)
38
+ crack (>= 0.1.7)
39
+ yajl-ruby (1.1.0)
40
+ yard (0.8.2.1)
41
+
42
+ PLATFORMS
43
+ ruby
44
+
45
+ DEPENDENCIES
46
+ badbill!
47
+ redcarpet
48
+ rspec
49
+ simplecov
50
+ webmock
51
+ yard
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2012 Jan-Erik Rediger <http://fnordig.de/about/>
2
+
3
+ Permission is hereby granted, free of charge, to any person ob-
4
+ taining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without restric-
6
+ tion, including without limitation the rights to use, copy, modi-
7
+ fy, merge, publish, distribute, sublicense, and/or sell copies of
8
+ the Software, and to permit persons to whom the Software is fur-
9
+ nished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
16
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONIN-
17
+ FRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
19
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,92 @@
1
+ BadBill - Billomat API Client
2
+ ===================
3
+
4
+ Simple but working API client for the [Billomat API][apidocu].
5
+
6
+ See the [API documentation][apidocu] for full documentation of all resources.
7
+
8
+ ## Features
9
+
10
+ * Fast and easy access to all resources the API provides
11
+ (not all resources are Ruby classes, yet)
12
+ * Full documentation.
13
+ * Test coverage as best as I can.
14
+ * Production-ready (it's for a job project).
15
+
16
+ ## What's working right now
17
+
18
+ The basic BadBill class allows access to all resources. It includes no syntactic sugar to work with, instead it just returns the data as a hash. This works for basic usage.
19
+
20
+ The following resources are currently implemented as its own class:
21
+
22
+ * [clients](http://www.billomat.com/en/api/invoices/) (`BadBill::Client`)
23
+ * [invoices](http://www.billomat.com/en/api/invoices/) (`BadBill::Invoices`)
24
+
25
+ These are the two I need right now.
26
+ Implementing new resources is easy. Pull Requests for others are welcome.
27
+
28
+ ## Requirements
29
+
30
+ * [yajl-ruby](https://github.com/brianmario/yajl-ruby)
31
+ * [faraday](https://github.com/technoweenie/faraday)
32
+ * [faraday_middleware](https://github.com/pengwynn/faraday_middleware)
33
+ * [hashie](https://github.com/intridea/hashie)
34
+
35
+ ## Installation
36
+
37
+ BadBill is just a `gem install badbill` away. Get an API key for the Billomat API on your profile page.
38
+
39
+ ## Examples
40
+
41
+ ### Basic Usage
42
+
43
+ bill = BadBill.new "billo", "18e40e14"
44
+ # => #<BadBill:0x00000001319d30 ...>
45
+ bill.get 'settings'
46
+ # => {"settings"=>
47
+ # {"invoice_intro"=>"",
48
+ # "invoice_note"=>"",
49
+ # ...}}
50
+ bill.put 'settings', settings: { invoice_intro: "Intro" }
51
+ # => {"settings"=>
52
+ # {"invoice_intro"=>"Intro",
53
+ # "invoice_note"=>"",
54
+ # ...}}
55
+
56
+ ### Using defined resource classes
57
+
58
+ BadBill.new "billo", "18e40e14"
59
+
60
+ BadBill::Invoice.all
61
+ # => [#<BadBill::Invoice:0x000000024caf98 @id="1" @data={...}>], ...]
62
+
63
+ invoice = BadBill::Invoice.find(1)
64
+ invoice.pdf
65
+ # => {"id"=>"1",
66
+ # "created"=>"2012-09-17T13:58:42+02:00",
67
+ # "invoice_id"=>"322791",
68
+ # "filename"=>"Invoice 322791.pdf",
69
+ # "mimetype"=>"application/pdf",
70
+ # "filesize"=>"90811",
71
+ # "base64file"=>"JVBERi0xLjM..."}
72
+ invoice.delete
73
+ # => true
74
+
75
+ BadBill::Invoice.create client_id: 1, date: "2012-09-18", note: "Thank you for your order", ...
76
+
77
+ ## Documentation
78
+
79
+ Documentation is online at [rubydoc.info](http://rubydoc.info/github/badboy/badbill/master/frames).
80
+
81
+ Generate locale documentation with `rake doc` ([yard](http://yardoc.org/) required).
82
+ Required Parameters and possible values won't be documentated here. See the [API documentation][apidocu] for that.
83
+
84
+ ## Contribute
85
+
86
+ See [CONTRIBUTING.md](/badboy/badbill/blob/master/CONTRIBUTING.md) for info.
87
+
88
+ ## License
89
+
90
+ See [LICENSE](/badboy/badbill/blob/master/LICENSE) for info.
91
+
92
+ [apidocu]: http://www.billomat.com/en/api/
data/Rakefile ADDED
@@ -0,0 +1,85 @@
1
+ require 'date'
2
+ require 'fileutils'
3
+ require 'rspec/core/rake_task'
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+
7
+ require 'yard'
8
+ YARD::Rake::YardocTask.new do |t|
9
+ t.files = ['lib/**/*.rb']
10
+ end
11
+
12
+ task :default => :spec
13
+ task :doc => :yard
14
+
15
+ ## helper functions
16
+
17
+ def name
18
+ @name ||= Dir['*.gemspec'].first.split('.').first
19
+ end
20
+
21
+ def version
22
+ line = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*.*/]
23
+ line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
24
+ end
25
+
26
+ def gemspec_file
27
+ "#{name}.gemspec"
28
+ end
29
+
30
+ def gem_file
31
+ "#{name}-#{version}.gem"
32
+ end
33
+
34
+ def replace_header(head, header_name)
35
+ head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
36
+ end
37
+
38
+ desc "Open an irb session preloaded with this library"
39
+ task :console do
40
+ sh "irb -rubygems -r ./lib/#{name}.rb"
41
+ end
42
+
43
+ ## release management tasks
44
+
45
+ desc "Commit, create tag v#{version} and build and push #{gem_file} to Rubygems"
46
+ task :release => :build do
47
+ sh "git commit --allow-empty -a -m 'Release #{version}'"
48
+ sh "git tag v#{version}"
49
+ sh "git push origin"
50
+ sh "git push origin v#{version}"
51
+ sh "gem push pkg/#{gem_file}"
52
+ end
53
+
54
+ desc "Build #{gem_file} into the pkg directory"
55
+ task :build => :gemspec do
56
+ FileUtils.mkdir_p 'pkg'
57
+ sh "gem build #{gemspec_file}"
58
+ sh "mv #{gem_file} pkg"
59
+ end
60
+
61
+ desc "Generate #{gemspec_file}"
62
+ task :gemspec do
63
+ # read spec file and split out manifest section
64
+ spec = File.read(gemspec_file)
65
+ head, manifest, tail = spec.split(" # = MANIFEST =\n")
66
+
67
+ # replace name version and date
68
+ replace_header(head, :name)
69
+ replace_header(head, :version)
70
+
71
+ # determine file list from git ls-files
72
+ files = `git ls-files`.
73
+ split("\n").
74
+ sort.
75
+ reject { |file| file =~ /^\./ }.
76
+ reject { |file| file =~ /^(doc|coverage|pkg)/ }.
77
+ map { |file| " ./#{file}" }.
78
+ join("\n")
79
+
80
+ # piece file back together and write
81
+ manifest = " s.files = %w[\n#{files}\n ]\n"
82
+ spec = [head, manifest, tail].join(" # = MANIFEST =\n")
83
+ File.open(gemspec_file, 'w') { |io| io.write(spec) }
84
+ puts "Updated #{gemspec_file}"
85
+ end
@@ -0,0 +1,118 @@
1
+ # encoding: utf-8
2
+
3
+ class BadBill
4
+ class BaseResource
5
+ include BadBill::Resource
6
+ extend BadBill::Resource
7
+
8
+ include BadBill::ForwardMethods
9
+
10
+ # ID of the resource.
11
+ attr_reader :id
12
+ # All data in a Hash.
13
+ attr_accessor :data
14
+
15
+ # Public: Create new resource object.
16
+ #
17
+ # @param [String,Integer] id The ID of the resource.
18
+ # @param [Hashie::Mash] data Resource data.
19
+ #
20
+ # All resources have an ID and data. Methods are proxied to the data object.
21
+ #
22
+ # @return [Resource] A new resource.
23
+ def initialize id, data
24
+ @id = id
25
+ @data = data
26
+ end
27
+
28
+ # Get all resources of this type.
29
+ #
30
+ # @param [Hash] filter An Hash of filter parameters,
31
+ # see the online documentation for allowed values.
32
+ #
33
+ # @return [Array<Resource>] All found resources.
34
+ def self.all filter={}
35
+ all = get resource_name, filter
36
+ all.__send__(resource_name).__send__(resource_name_singular).map do |res|
37
+ new res.id, res
38
+ end
39
+ end
40
+
41
+ # Get the resource with the given ID.
42
+ #
43
+ # @param [String,Integer] id ID of the resource.
44
+ #
45
+ # @return [Resource] New resource with id and data set.
46
+ def self.find id
47
+ c = get resource_name, id
48
+ new id, c.__send__(resource_name_singular)
49
+ end
50
+
51
+ # Create a new resource with the given parameters.
52
+ #
53
+ # @param [Hash] params A Hash-like object of data.
54
+ # See the online documentation for allowed values.
55
+ #
56
+ # @return [Resource] New resource with id and data set.
57
+ def self.create params
58
+ res = post(resource_name, {resource_name_singular => params})
59
+ res_data = res.__send__(resource_name_singular)
60
+ new res_data.id, res_data
61
+ end
62
+
63
+ # Save any changed data.
64
+ #
65
+ # @return [Boolean] True if successfull, false otherwise.
66
+ def save
67
+ @data.id = id
68
+ resp = put resource_name, id, {resource_name_singular => @data}
69
+ !resp
70
+ end
71
+
72
+ # Delete this resource.
73
+ #
74
+ # @return [Boolean] True if successfull, false otherwise.
75
+ def delete
76
+ # Hack: We can't call #delete here, because we ARE #delete
77
+ resp = call resource_name, id, nil, :delete
78
+ !resp
79
+ end
80
+
81
+ # Protect overriding of the ID.
82
+ #
83
+ # @raise [NoMethodError] because the ID may not be overwritten.
84
+ def id= *args
85
+ raise NoMethodError, "undefined method `id=' for '#{self.inspect.sub(/ .+/, '>')}`"
86
+ end
87
+
88
+ private
89
+
90
+ def resource_name_singular
91
+ self.class.resource_name_singular
92
+ end
93
+
94
+ def resource_name
95
+ self.class.resource_name
96
+ end
97
+
98
+ # The singular resource name for use with the API.
99
+ #
100
+ # By default this is just the Class name, but it may be overridden.
101
+ # Make sure to overwrite resource_name, too.
102
+ #
103
+ # @return [String] The singular resource name.
104
+ def self.resource_name_singular
105
+ @resource_name_singular ||= self.name.split(/::/).last.downcase
106
+ end
107
+
108
+ # The resource name (plural) for use with the API.
109
+ #
110
+ # By default this is just the `resource_name_singular` with an appended 's'
111
+ # See #resource_name_singular
112
+ #
113
+ # @return [String] The resource name.
114
+ def self.resource_name
115
+ @resource_name ||= "#{resource_name_singular}s"
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,32 @@
1
+ # encoding: utf-8
2
+
3
+ class BadBill
4
+ # The clients resource handles all clients.
5
+ #
6
+ # See http://www.billomat.com/en/api/clients/
7
+ class Client < BaseResource
8
+ attr_writer :myself
9
+
10
+ def initialize id, data
11
+ super
12
+ @myself = false
13
+ end
14
+
15
+ # Fetch information about yourself.
16
+ #
17
+ # @return [Client] A new resource.
18
+ def self.myself
19
+ c = get 'clients', 'myself'
20
+ client = new c.client.id, c.client
21
+ client.myself = true
22
+ client
23
+ end
24
+
25
+ # Indicates wether this resource is yourself or not.
26
+ #
27
+ # @return [Boolean] Wether or not this resource is yourself.
28
+ def myself?
29
+ @myself
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,30 @@
1
+ # encoding: utf-8
2
+
3
+ class BadBill
4
+ # Forward all methods to an underlying object called data.
5
+ #
6
+ # This acts like a proxy object.
7
+ module ForwardMethods
8
+ # Respond to method_missing to send down the line.
9
+ #
10
+ # As Hashie::Mash#method_missing does not respond with true to an
11
+ # assignment request, we only check for the method name without the equal sign.
12
+ def method_missing(method_name, *arguments, &block)
13
+ if data.respond_to?(method_name.to_s.sub(/=$/, ''))
14
+ data.send(method_name, *arguments, &block)
15
+ else
16
+ super
17
+ end
18
+ end
19
+
20
+ # Proxy respond_to? to the underlying object if needed.
21
+ #
22
+ # If playing with method_missing define respond_to? too.
23
+ # See http://robots.thoughtbot.com/post/28335346416/always-define-respond-to-missing-when-overriding
24
+ #
25
+ # @return [Boolean] True if the class itself or the proxied object responds to the given method.
26
+ def respond_to?(method_name, include_private = false)
27
+ super || data.respond_to?(method_name, include_private)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,106 @@
1
+ # encoding: utf-8
2
+
3
+ require 'base64'
4
+
5
+ class BadBill
6
+ # The resource handles all invoices.
7
+ #
8
+ # See http://www.billomat.com/en/api/invoices
9
+ class Invoice < BaseResource
10
+ # Get the PDF invoice.
11
+ #
12
+ #
13
+ # @return [Hashie::Mash] Hash containing the ID, filesize, base64file and
14
+ # other parameters.
15
+ # See http://www.billomat.com/en/api/invoices for
16
+ # all parameters.
17
+ def pdf
18
+ resp = get resource_name, "#{id}/pdf"
19
+ resp.pdf
20
+ end
21
+
22
+ # Closes a statement in the draft status (DRAFT). Here, the
23
+ # status of open (OPEN) or overdue (OVERDUE) is set and a PDF is
24
+ # generated and stored in the file system.
25
+ #
26
+ # @param [String,Integer] template_id ID of the Template used to create pdf,
27
+ # If not set, default template is used.
28
+ #
29
+ # @return [Boolean] Wether or not the call was successfull.
30
+ def complete template_id=nil
31
+ data = { complete: {} }
32
+ data[:complete] = { template_id: template_id } if template_id
33
+ resp = put resource_name, "#{id}/complete", data
34
+
35
+ !resp
36
+ end
37
+
38
+ # Cancel an invoice.
39
+ #
40
+ # @return [Boolean] Wether or not the call was successfull.
41
+ def cancel
42
+ resp = put resource_name, "#{id}/cancel"
43
+ !resp
44
+ end
45
+
46
+ # Sends an invoice by email.
47
+ #
48
+ # @param [String] to Recipient of the email.
49
+ # cc and bcc can be set in the `more` Hash.
50
+ # @param [String] from Sender email address.
51
+ # @param [String] subject Subject for the email.
52
+ # @param [String] body Body for the email.
53
+ # @param [Hash] more Hash-like object including more options.
54
+ # See online documentation for all allowed parameters.
55
+ #
56
+ # from, subject or body can be replaced by more.
57
+ #
58
+ # @return [Boolean] Wether or not the call was successfull.
59
+ def email to, from=nil, subject=nil, body=nil, more={}
60
+ data = { recipients: {} }
61
+
62
+ if more.empty? && from.kind_of?(Hash)
63
+ more = from
64
+ from = nil
65
+ end
66
+
67
+ if more.empty? && subject.kind_of?(Hash)
68
+ more = subject
69
+ subject = nil
70
+ end
71
+
72
+ if more.empty? && body.kind_of?(Hash)
73
+ more = body
74
+ body = nil
75
+ end
76
+
77
+ data[:from] = from if from
78
+ data[:subject] = subject if subject
79
+ data[:body] = body if body
80
+
81
+ data.merge! more
82
+ data[:recipients][:to] = to
83
+
84
+ resp = post resource_name, "#{id}/email", email: data
85
+ !resp
86
+ end
87
+
88
+ # Uploads a digital signature for a given invoice.
89
+ #
90
+ # The status of the invoice may not be DRAFT.
91
+ #
92
+ # @param [String,#read] file file name or a object responding to #read.
93
+ # This will be base64-encoded before send.
94
+ #
95
+ # @return [Boolean] false if status is DRAFT or another error occured. true if everything was successfull.
96
+ def upload_signature file
97
+ return false if data.status == 'DRAFT'
98
+
99
+ file = File.open(file, 'r') unless file.respond_to?(:read)
100
+
101
+ base64 = Base64.encode64 file.read
102
+ put resource_name, "#{id}/upload-signature", { signature: { base64file: base64 } }
103
+ true
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,34 @@
1
+ # encoding: utf-8
2
+
3
+ class BadBill
4
+ # Forward requests to the underlying connection object.
5
+ #
6
+ # This module is included in BadBill::BaseResource.
7
+ module Resource
8
+ # @param (see BadBill#get)
9
+ def get resource, id='', options=nil
10
+ call resource, id, options, :get
11
+ end
12
+
13
+ # @param (see BadBill#post)
14
+ def post resource, id='', options=nil
15
+ call resource, id, options, :post
16
+ end
17
+
18
+ # @param (see BadBill#put)
19
+ def put resource, id='', options=nil
20
+ call resource, id, options, :put
21
+ end
22
+
23
+ # @param (see BadBill#delete)
24
+ def delete resource, id='', options=nil
25
+ call resource, id, options, :delete
26
+ end
27
+
28
+ # @param (see BadBill#call)
29
+ def call resource, id='', options=nil, method=:get
30
+ raise BadBill::NoConnection, "No connection. Use BadBill.new first." if BadBill.connection.nil?
31
+ BadBill.connection.call resource, id, options, method
32
+ end
33
+ end
34
+ end