sk-api 1.0.6 → 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.
data/README.rdoc CHANGED
@@ -1,11 +1,9 @@
1
1
  = SalesKing Api
2
2
 
3
- This plugin is used internally by SalesKing to deliver the API and
4
- can be used externally as an API client. The code is used in production at
5
- https://www.SalesKing, http://texprovider.de and http://planio.de
3
+ This gem provides an api client using ActiveResource and also provides the API's JSON-Schema
4
+ In the near future this will be decoupled and you will find links to the new gems here.
6
5
 
7
- The SalesKing API client uses ActiveResource, with some fixes due to json parsing.
8
- For now it only supports JSON, XML comes later.
6
+ The SalesKing API client uses JSON and ActiveResource, with some fixes due to json parsing.
9
7
 
10
8
  To be able to use the API you must have an SalesKing account and API access.
11
9
  Than you can start writing your own middelware-stack.
@@ -17,13 +15,209 @@ and support.
17
15
 
18
16
  gem install sk-api
19
17
 
20
- == Example
18
+ Use with bundler
21
19
 
22
- client = SkApi::Resources::Client.new(:last_name=>'Eder')
23
- client.first_name = "Meister"
24
- client.save
20
+ gem "sk-api", :require => 'sk_api'
21
+
22
+ == Usage
23
+
24
+ SalesKing's api interface is RESTful(mostly) and returns & accepts JSON data.
25
+ All resources such as clients, invoices, products can be accessed via URL's
26
+ through standard HTTP methods GET, POST, PUT and DELETE.
27
+
28
+ To give it quick shot, point your browser to my-account.salesking.eu/api/clients.json
29
+ (while beeing logged in) and you will see the JSON in the response.
30
+ This is an example of a GET-request issued by your browser.
31
+
32
+ <b>Request Method overview:</b>
33
+ GET => read
34
+ POST => create new data
35
+ PUT => update data
36
+ DELETE => delete data
37
+
38
+ Want to know more about REST style webservices?
39
+ * http://en.wikipedia.org/wiki/Representational_State_Transfer
40
+ * http://www.google.com/search?q=REST+site%3Awww.infoq.com
41
+
42
+ === Authentification & Safety
43
+
44
+ Authentification is done with HTTP basic using your SalesKing login email and password.
45
+ For a production environment it is advisable to create a user, per api
46
+ client-software, and restrict his rights with our build in role-system.
47
+
48
+ === Toolz
49
+
50
+ Since browsers do not support PUT/DELETE methods you can use CURL, a linux
51
+ command-line http client, for testing. And of course any http library supporting
52
+ http-basic-auth can be used.
53
+
54
+ * JSONView FF-Plugin - view json in firefox https://addons.mozilla.org/de/firefox/addon/10869/
55
+ * JSONovich FF-Plugin - https://addons.mozilla.org/de/firefox/addon/10122/
56
+ * A java GUI REST client http://code.google.com/p/rest-client/
57
+
58
+ === GET / Show data
59
+
60
+ Get a list of resources:
61
+ GET xy.salesking.eu/api/invoices
62
+
63
+ Get a single resource
64
+ GET xy.salesking.eu/invoices/:id
65
+
66
+ Using CURL
67
+
68
+ curl -u your@login-mail.com:password \
69
+ https://demo.salesking.eu/clients.json
70
+
71
+ Returned JSON data for a listings, abbreviated:
72
+ {
73
+ "clients": [ # array of resources => clients
74
+ { "client": { # a single resource
75
+ "number": "0800013", ..
76
+ "links":{} #links to actions for the resource (comming soon)
77
+ }, ...
78
+ ],
79
+ collection": { # information about the collection
80
+ "total_pages": 1,
81
+ "total_entries": 3,
82
+ "current_page": 1,
83
+ "per_page": 30
84
+ },
85
+ "links": { # links for the collection
86
+ "prev": "/api/clients?page=0",
87
+ "next": "/api/clients?page=2",
88
+ "self": "/api/clients?page=1"
89
+ },
90
+ }
91
+
92
+ ==== Filtering
93
+
94
+ Filtering data can be achived by adding a filter parameter to the url.
95
+
96
+ All objects which can be tagged(clients, documents, products) can be filter by a tag param, taking one or multiple tags:
97
+ GET xy.salesking.eu/api/invoices?filter[tags]=Agentur
98
+ GET xy.salesking.eu/api/invoices?filter[tags]=Agentur Grafik
99
+ # find all objects without tags
100
+ GET xy.salesking.eu/api/invoices?filter[not_tagged]=1
101
+
102
+ All objects have a q-search available. The fields to be searched in can be seen inside SalesKing, behind the search field above list.
103
+ GET xy.salesking.eu/api/invoices?q=0800013
104
+ GET xy.salesking.eu/api/invoices?filter[q]=0800013
105
+
106
+ Document specific filters
107
+ # find all documents for a client
108
+ GET xy.salesking.eu/api/invoices?filter[client]=:client_id
109
+
110
+ Client filter:
111
+ # find clients organisation or lastname starting with A
112
+ GET xy.salesking.eu/api/clients?filter[letter]=A
113
+
114
+ === POST / Create data
115
+
116
+ To create a resource you can use the gem with its methods or make a POST request containing the json for a single resource.
117
+ The JSON format can be seen in each of the resources: http://github.com/salesking/sk-api/tree/master/lib/resources/
118
+
119
+ POST https://my-sub.salesking.eu/invoices.json content-type application/json
120
+
121
+ This a minimal json to create a draft invoice:
122
+
123
+ {"invoice": {
124
+ "due_days": 14,
125
+ "line_items": [
126
+ {
127
+ "quantity_unit": "Stuck",
128
+ "tax": 19,
129
+ "price_single": 0,
130
+ "position": 1,
131
+ "quantity": 21,
132
+ "description": "dep38usabq4Boxen kostenlos"
133
+ },
134
+ {
135
+ "quantity_unit": "Stuck",
136
+ "tax": 19,
137
+ "price_single": 0.042022,
138
+ "position": 2,
139
+ "quantity": 21,
140
+ "description": "dep38usabq4 Boxen"
141
+ }
142
+ ],
143
+ "title": "Dabei",
144
+ "client_id": "c5d5xIwZGr3R56abxfpGMl",
145
+ "address_field": "Wurst Bolle\nHalve-Hahn Alle 4711\n50733 Köln-Nippes"
146
+ }}
147
+
148
+ Some quick hints on document creation:
149
+
150
+ * The line items array does not need prefixes .. (sry little inconsistency here)
151
+ * Dates are formatted like <tt>yyyy-mm-dd 2010-09-08</tt>
152
+ * <tt>"price_total": 0.88, "price_tax": 0.167</tt> can be ommited are calculated by sk => read only
153
+ * <tt>due_date</tt> if left out will be auto calculated from due days. If date and due date are given due_days are not taken into consideration.
154
+ * <tt>status: draft</tt> is default, so can be omitted
155
+ * <tt>client_id</tt> must be given as uuid of the client object
156
+ * line items with quantity of 0 are kicked
157
+ * If you create the doc as draft you can leave out any dates. When “opened” in salesking the date will be set and due date is calculated from due_days
158
+ * If you create the invoice as "open" you must set a number(and date), by now there is no api method to get the next number.
159
+ * The proposed workflow is to create the document as draft and continue (open/send/close) in SalesKing.
160
+
161
+
162
+ For more information on how to use take a look into the tests/specs:
163
+
164
+ http://github.com/salesking/sk-api/tree/master/spec/resources/
165
+
166
+ === PUT / Update data
167
+
168
+ To Update a client:
169
+
170
+ PUT ( application/json ) to ../api/clients/:id
171
+ {
172
+ 'client':
173
+ {
174
+ 'gender':'male',
175
+ 'first_name': "Andrew"
176
+ }
177
+ }
178
+
179
+ === DELETE / kill data
180
+
181
+ To delete a record simply issue a DELETE request to a resource url:
182
+
183
+ DELETE https://demo.salesking.eu/clients/:id
184
+
185
+ == Ruby Example
186
+
187
+ Since the sk-api gem provides a wrapper around the whole url/request stuff its
188
+ pretty straight forward to use:
189
+
190
+ class SomeMiddleware
191
+ include SKApi::Resources
192
+
193
+ def initialize()
194
+ # init api connection => just an example depending on your code structure do it wherever you want
195
+ set_api_connection
196
+ end
197
+
198
+ def create_client(last_name)
199
+ # create a new client object
200
+ client = SKApi::Resources::Client.new(:last_name=> last_name)
201
+ # set more data
202
+ client.first_name = "Meister"
203
+ # save it, which also does the remote saving action
204
+ client.save
205
+ end
206
+
207
+ def set_api_connection
208
+ SKApi::Resources::Base.set_connection({
209
+ :site => 'my_sub.salesking.eu',
210
+ :user => 'demo@salesking.eu',
211
+ :password => 'password',
212
+ :format => :json # symbol expected
213
+ })
214
+ end
215
+ end
216
+
217
+ ##### use it
218
+ a = SomeMiddleware.new
219
+ a.create_client('eder')
25
220
 
26
- For more information on how to use take a look into the specs.
27
221
 
28
222
  == Salesking API Resources
29
223
 
@@ -34,7 +228,10 @@ Right now the following resources are available:
34
228
  * products
35
229
  * credit_notes
36
230
 
37
- == Tests
231
+ See classes here:
232
+ http://github.com/salesking/sk-api/tree/master/lib/resources/
233
+
234
+ == Tests / Specs
38
235
 
39
236
  To run the test you must insert some credentials into the spec helper.
40
237
  We will gladly provide you with a test account on one of our development
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.6
1
+ 1.1.0
File without changes
@@ -27,7 +27,7 @@ module ActiveResource
27
27
  end
28
28
  end
29
29
 
30
- # Ooverridden to grab the data(= clients-collection) from json:
30
+ # Overridden to grab the data(= clients-collection) from json:
31
31
  # { 'collection'=> will_paginate infos,
32
32
  # 'links' => prev/next links
33
33
  # 'clients'=> [data], << what we need
@@ -37,15 +37,4 @@ module ActiveResource
37
37
  collection.collect! { |record| instantiate_record(record, prefix_options) }
38
38
  end
39
39
  end
40
- end
41
-
42
- # Force json decoding using Rufus
43
- module ActiveResource
44
- module Formats
45
- module JsonFormat
46
- def decode(json)
47
- Rufus::Json.decode(json)
48
- end
49
- end
50
- end
51
40
  end
@@ -1,5 +1,5 @@
1
1
  module ActiveResource
2
-
2
+
3
3
  module Validations
4
4
  # Validate a resource and save (POST) it to the remote web service.
5
5
  def save_with_validation
@@ -13,7 +13,7 @@ module ActiveResource
13
13
  errors.from_json(error.response.body)
14
14
  end
15
15
  false
16
- end
16
+ end
17
17
  end
18
18
 
19
19
  class Errors
@@ -0,0 +1,23 @@
1
+ module ActiveResource
2
+ # Overridden methods to suit SalesKing.
3
+ # Some changes might be kicked when AR 3.0 is out
4
+ class Base
5
+
6
+ # override ARes method to parse only the client part
7
+ def load_attributes_from_response(response)
8
+ if response['Content-Length'] != "0" && response.body.strip.size > 0
9
+ load( self.class.format.decode(response.body)[self.class.element_name] )
10
+ end
11
+ end
12
+
13
+ # Overridden to grab the data(= clients-collection) from json:
14
+ # { 'collection'=> will_paginate infos,
15
+ # 'links' => prev/next links
16
+ # 'clients'=> [data], << what we need
17
+ # }
18
+ def self.instantiate_collection(collection, prefix_options = {})
19
+ collection = collection[ self.element_name.pluralize ] if collection.is_a?(Hash)
20
+ collection.collect! { |record| instantiate_record(record, prefix_options) }
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,13 @@
1
+ module ActiveResource
2
+ class Errors < ActiveModel::Errors
3
+ # Patched cause we dont need no attribute name magic .. and its just simpler
4
+ # orig version is looking up the humanized name of the attribute in the error
5
+ # message, which we dont supply => only field name is used in returned error msg
6
+ def from_array(messages, save_cache=false)
7
+ clear unless save_cache
8
+ messages.each do |msg|
9
+ add msg[0], msg[1]
10
+ end
11
+ end
12
+ end #Errors
13
+ end
@@ -16,7 +16,7 @@ module SKApi
16
16
  "id" => {"type" => "string", "identity" => true, "readonly" => true},
17
17
  "address1" => {"type" => "string", "optional" => true},
18
18
  "address2" => {"type" => "string", "optional" => true},
19
- "city" => {"type" => "string", "optional" => true},
19
+ "city" => {"type" => "string"},
20
20
  "country" => {"type" => "string", "optional" => true},
21
21
  "state" => {"type" => "string", "optional" => true},
22
22
  "zip" => {"type" => "string", "optional" => true},
@@ -13,7 +13,7 @@ module SKApi
13
13
 
14
14
  def self.schema_props
15
15
  {
16
- "id" => {"type" => "string", "identity" => true , "readonly" => true},
16
+ "id" => {"type" => "string", "identity" => true, "optional" => true, "readonly" => true},
17
17
  "number" => {"type" => "string", "optional" => true},
18
18
  "organisation" => {"type" => "string"},
19
19
  "first_name" => {"type" => "string", "optional" => true},
@@ -45,7 +45,7 @@ module SKApi
45
45
  "created_at" => {"type" => "string", "format" =>"date-time", "optional" => true, "readonly" => true},
46
46
  "updated_at" => {"type" => "string", "format" =>"date-time", "optional" => true, "readonly" => true},
47
47
  "address_field" => {"type" => "string", "optional" => true, "readonly" => true},
48
- "addresses" => {"type" => "array"},
48
+ "addresses" => {"type" => "array","properties" => SKApi::Resources::Address.schema_props, "optional" => true}
49
49
  }
50
50
  end #schema_props
51
51
 
@@ -6,6 +6,19 @@ module SKApi
6
6
  save_with_validation
7
7
  end
8
8
 
9
+ def open
10
+ self.put(:open, :id => self.id) # PUT /invoices/:id/open
11
+ end
12
+
13
+ # rethink
14
+ def print(template_id)
15
+ self.get(:print, :template_id => template_id) # GET /invoices.pdf?template_id=:ids
16
+ end
17
+
18
+ def mail(template_id, cc, bcc)
19
+ self.get(:print, :template_id => template_id) # GET /invoices.pdf?template_id=:ids
20
+ end
21
+
9
22
  ##########################################################################
10
23
  # Class methods
11
24
  ##########################################################################
@@ -41,8 +54,14 @@ module SKApi
41
54
 
42
55
  def self.api_links
43
56
  #internal links on fields=> id => salesking.eu/clients/4567.json
44
- #external links to actions and related objects => invoeis => salesking.eu/clients/4567/invoices.json
45
57
  [:edit, :destroy, :copy, :print, :show, :payments, :payment_new]
58
+ # [ :edit,
59
+ # :destroy,
60
+ # { :copy =>"invoices/new?source=#{self.id}" },
61
+ # :open => "PUT invoices/#{self.id}/open",
62
+ # :print,
63
+ # :show, ]
64
+
46
65
  end
47
66
  end
48
67
  end
@@ -22,7 +22,6 @@ module SKApi
22
22
  "quantity_unit"=> {"type" => "string", "optional" => true},
23
23
  "tag_list" => {"type" => "string", "optional" => true},
24
24
  "lock_version" => {"type" => "integer", "readonly" => true, "optional" => true},
25
- "published_at" => {"type" => "string", "format" =>"date", "optional" => true},
26
25
  "created_at" => {"type" => "string", "format" =>"date-time", "optional" => true, "readonly" => true },
27
26
  "updated_at" => {"type" => "string", "format" =>"date-time", "optional" => true, "readonly" => true },
28
27
  }
data/lib/sk_api.rb CHANGED
@@ -1,29 +1,28 @@
1
- require 'rubygems'
1
+ #require 'rubygems'
2
2
  require 'active_resource'
3
+ require 'active_resource/version'
3
4
  # patches are for specific AR version
4
- #gem 'activeresource' , '=2.3.4'
5
+ # switch A-resource patch includes
5
6
 
6
- unless RUBY_PLATFORM =~ /java/
7
- require 'yajl'
8
- require 'rufus-json'
9
- Rufus::Json.backend = :yajl
10
- else
11
- require 'json'
12
- require 'rufus-json'
13
- Rufus::Json.backend = :json
7
+ if ActiveResource::VERSION::MAJOR == 3
8
+ require 'patches/ar3/base'
9
+ require 'patches/ar3/validations'
10
+ elsif ActiveResource::VERSION::MAJOR < 3
11
+ require 'patches/ar2/validations'
12
+ require 'patches/ar2/base'
14
13
  end
15
14
 
16
15
  # utilities
17
- require File.dirname(__FILE__) + '/utils/field_map'
18
- require File.dirname(__FILE__) + '/utils/serializer'
16
+ require 'utils/field_map'
17
+ require 'utils/serializer'
19
18
 
20
19
  #resources
21
- require File.dirname(__FILE__) + '/activeresource_patches/validations'
22
- require File.dirname(__FILE__) + '/activeresource_patches/base'
23
- require File.dirname(__FILE__) + '/resources/base'
24
- require File.dirname(__FILE__) + '/resources/product'
25
- require File.dirname(__FILE__) + '/resources/client'
26
- require File.dirname(__FILE__) + '/resources/address'
27
- require File.dirname(__FILE__) + '/resources/invoice'
28
- require File.dirname(__FILE__) + '/resources/credit_note'
29
- require File.dirname(__FILE__) + '/resources/line_item'
20
+ require 'resources/base'
21
+ require 'resources/product'
22
+ require 'resources/client'
23
+ require 'resources/address'
24
+ require 'resources/invoice'
25
+ require 'resources/credit_note'
26
+ require 'resources/line_item'
27
+
28
+ # TODO before post kick empty fields and readonly's
@@ -6,7 +6,7 @@ module SKApi
6
6
  # f.ex. in the clients api controller
7
7
  # => SKApi::Resources::Client.to_json(a_client)
8
8
  # This way you can keep your API client up to date by using the resources and
9
- # relying on SKApi::Resources::Client.api_fields
9
+ # relying on SKApi::Resources::Client.schema
10
10
  module Serializer
11
11
  def self.included(base)
12
12
  base.extend ClassMethods
@@ -42,8 +42,8 @@ module SKApi
42
42
  # ex: data['invoice']['client'] = SKApi::Models::Client.to_hash_from_schema(client)
43
43
  data[obj_class_name][field] = klass.to_hash_from_schema(rel_obj)
44
44
  end
45
- else # a simple field which can be directly called
46
- data[obj_class_name][field] = obj.send(field)
45
+ else # a simple field which can be directly called, only added of objects know its
46
+ data[obj_class_name][field] = obj.send(field) if obj.respond_to?(field.to_sym)
47
47
  end
48
48
  end
49
49
  data
@@ -52,7 +52,7 @@ module SKApi
52
52
  def to_json(obj)
53
53
  data = self.to_hash_from_schema(obj)
54
54
  # data[:links] = self.api_links
55
- Rufus::Json.encode(data)
55
+ ActiveSupport::JSON.encode(data)
56
56
  end
57
57
 
58
58
  end #ClassMethods
data/sk-api.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{sk-api}
8
- s.version = "1.0.6"
8
+ s.version = "1.1.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Georg Leciejewski"]
12
- s.date = %q{2010-04-16}
12
+ s.date = %q{2010-11-07}
13
13
  s.description = %q{Interact with SalesKing}
14
14
  s.email = %q{gl@salesking.eu}
15
15
  s.extra_rdoc_files = [
@@ -23,9 +23,11 @@ Gem::Specification.new do |s|
23
23
  "Rakefile",
24
24
  "VERSION",
25
25
  "init.rb",
26
- "lib/activeresource_patches/README",
27
- "lib/activeresource_patches/base.rb",
28
- "lib/activeresource_patches/validations.rb",
26
+ "lib/patches/README",
27
+ "lib/patches/ar2/base.rb",
28
+ "lib/patches/ar2/validations.rb",
29
+ "lib/patches/ar3/base.rb",
30
+ "lib/patches/ar3/validations.rb",
29
31
  "lib/resources/address.rb",
30
32
  "lib/resources/base.rb",
31
33
  "lib/resources/client.rb",
@@ -53,7 +55,7 @@ Gem::Specification.new do |s|
53
55
  s.homepage = %q{http://github.com/salesking/sk-api}
54
56
  s.rdoc_options = ["--charset=UTF-8"]
55
57
  s.require_paths = ["lib"]
56
- s.rubygems_version = %q{1.3.6}
58
+ s.rubygems_version = %q{1.3.7}
57
59
  s.summary = %q{Interact with SalesKing}
58
60
  s.test_files = [
59
61
  "spec/resources/client_spec.rb",
@@ -68,7 +70,7 @@ Gem::Specification.new do |s|
68
70
  current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
69
71
  s.specification_version = 3
70
72
 
71
- if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
73
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
72
74
  s.add_development_dependency(%q<rspec>, [">= 0"])
73
75
  else
74
76
  s.add_dependency(%q<rspec>, [">= 0"])
@@ -1,4 +1,4 @@
1
- require "#{File.dirname(__FILE__)}/../spec_helper"
1
+ require 'spec/spec_helper'
2
2
 
3
3
  describe SKApi::Resources::Client, "in general" do
4
4
 
@@ -59,9 +59,10 @@ describe SKApi::Resources::Client, "in general" do
59
59
  client = SKApi::Resources::Client.find(@client.id)
60
60
  # convert to json and read raw without activeresource assigning classes
61
61
  json = client.to_json
62
- obj = Rufus::Json.decode(json)
62
+ obj = ActiveSupport::JSON.decode(json)
63
+ # puts obj['client']['organisation']
63
64
  lambda {
64
- JSON::Schema.validate(obj, SKApi::Resources::Client.schema)
65
+ JSON::Schema.validate(obj['client'], SKApi::Resources::Client.schema)
65
66
  }.should_not raise_error
66
67
  end
67
68
 
@@ -83,6 +84,7 @@ describe SKApi::Resources::Client, "with addresses" do
83
84
  #setup test client to work with
84
85
  @client = SKApi::Resources::Client.new(:organisation=>'Second from testing API2',
85
86
  :addresses => [{ :zip => '50374', :city => 'Cologne' }] )
87
+
86
88
  @client.save
87
89
  end
88
90
 
@@ -1,4 +1,4 @@
1
- require "#{File.dirname(__FILE__)}/../spec_helper"
1
+ require 'spec/spec_helper'
2
2
 
3
3
  describe SKApi::Resources::CreditNote, "in general" do
4
4
 
@@ -25,11 +25,12 @@ describe SKApi::Resources::CreditNote, "in general" do
25
25
  }.should raise_error(ActiveResource::ResourceNotFound)
26
26
  end
27
27
 
28
- it "should create a doc" do
28
+ it "should create a doc and use default before after text" do
29
29
  @doc.errors.should be_empty
30
+ @doc.notes_before.should_not be_empty
30
31
  @doc.new?.should be_false
31
32
  end
32
-
33
+
33
34
  it "should fail create a doc" do
34
35
  doc = SKApi::Resources::CreditNote.new()
35
36
  doc.save.should == false
@@ -44,12 +45,11 @@ describe SKApi::Resources::CreditNote, "in general" do
44
45
 
45
46
  it "should validate raw json object with schema" do
46
47
  doc = SKApi::Resources::CreditNote.find(@doc.id)
47
- # doc.number.should=='23'
48
48
  # convert to json and read raw without activeresource assigning classes
49
49
  json = doc.to_json
50
- obj = Rufus::Json.decode(json)
50
+ obj = ActiveSupport::JSON.decode(json)
51
51
  lambda {
52
- JSON::Schema.validate(obj, SKApi::Resources::CreditNote.schema)
52
+ JSON::Schema.validate(obj['credit_note'], SKApi::Resources::CreditNote.schema)
53
53
  }.should_not raise_error
54
54
  end
55
55