versacommerce_api 1.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.
Files changed (45) hide show
  1. data/.gitignore +18 -0
  2. data/.ruby-version +1 -0
  3. data/CHANGELOG +2 -0
  4. data/CONTRIBUTORS +0 -0
  5. data/Gemfile +4 -0
  6. data/Gemfile.lock +59 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +200 -0
  9. data/RELEASING +16 -0
  10. data/Rakefile +1 -0
  11. data/bin/versacommerce +4 -0
  12. data/lib/active_resource/base_ext.rb +22 -0
  13. data/lib/active_resource/connection_ext.rb +31 -0
  14. data/lib/active_resource/detailed_log_subscriber.rb +19 -0
  15. data/lib/active_resource/disable_prefix_check.rb +8 -0
  16. data/lib/active_resource/json_errors.rb +21 -0
  17. data/lib/versacommerce_api.rb +21 -0
  18. data/lib/versacommerce_api/associatable.rb +25 -0
  19. data/lib/versacommerce_api/cli.rb +164 -0
  20. data/lib/versacommerce_api/countable.rb +9 -0
  21. data/lib/versacommerce_api/resources.rb +2 -0
  22. data/lib/versacommerce_api/resources/base.rb +42 -0
  23. data/lib/versacommerce_api/resources/billing_addres.rb +9 -0
  24. data/lib/versacommerce_api/resources/collection.rb +6 -0
  25. data/lib/versacommerce_api/resources/customer.rb +6 -0
  26. data/lib/versacommerce_api/resources/item.rb +9 -0
  27. data/lib/versacommerce_api/resources/link.rb +15 -0
  28. data/lib/versacommerce_api/resources/linklist.rb +11 -0
  29. data/lib/versacommerce_api/resources/order.rb +22 -0
  30. data/lib/versacommerce_api/resources/page.rb +6 -0
  31. data/lib/versacommerce_api/resources/payment.rb +6 -0
  32. data/lib/versacommerce_api/resources/product.rb +55 -0
  33. data/lib/versacommerce_api/resources/product_image.rb +67 -0
  34. data/lib/versacommerce_api/resources/property.rb +6 -0
  35. data/lib/versacommerce_api/resources/shipment.rb +11 -0
  36. data/lib/versacommerce_api/resources/shipping_addres.rb +9 -0
  37. data/lib/versacommerce_api/resources/shop.rb +10 -0
  38. data/lib/versacommerce_api/resources/variant.rb +7 -0
  39. data/lib/versacommerce_api/session.rb +88 -0
  40. data/lib/versacommerce_api/version.rb +3 -0
  41. data/spec/resources/order_spec.rb +71 -0
  42. data/spec/resources/product_spec.rb +73 -0
  43. data/spec/spec_helper.rb +18 -0
  44. data/versacommerce_api.gemspec +37 -0
  45. metadata +242 -0
@@ -0,0 +1,21 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ require 'active_resource'
4
+ require 'active_support/core_ext/class/attribute_accessors'
5
+ require 'digest/md5'
6
+ require 'base64'
7
+ require 'active_resource/connection_ext'
8
+ require 'active_resource/detailed_log_subscriber'
9
+ require 'active_resource/json_errors'
10
+ require 'active_resource/disable_prefix_check'
11
+ require 'active_resource/base_ext'
12
+
13
+ module VersacommerceAPI
14
+ # Your code goes here...
15
+ end
16
+
17
+ require "versacommerce_api/associatable"
18
+ require "versacommerce_api/version"
19
+ require 'versacommerce_api/countable'
20
+ require 'versacommerce_api/resources'
21
+ require 'versacommerce_api/session'
@@ -0,0 +1,25 @@
1
+ module VersacommerceAPI
2
+
3
+ module Associatable
4
+ def associated_resource association, pluralize=true
5
+ association = association.pluralize if pluralize
6
+
7
+ if self.new? or attributes[association]
8
+ attributes[association]
9
+ else
10
+ # dont create traffic if we know already there are no associated records...
11
+ return [] if self.respond_to?("#{association}_count") && self.send("#{association}_count") == 0 && pluralize
12
+ klass = "VersacommerceAPI::#{association.classify}".constantize
13
+ # ensure we don´t have the association already included
14
+ attributes[association] ||= begin
15
+ resource_name = self.class.name.split("::").last.tableize
16
+ klass.prefix = "/api/#{resource_name}/#{self.id}/"
17
+ pluralize ? klass.find(:all) : klass.find(:one)
18
+ end
19
+ klass.root!
20
+ attributes[association]
21
+ end
22
+ end
23
+ end
24
+
25
+ end
@@ -0,0 +1,164 @@
1
+ require 'thor'
2
+ require 'abbrev'
3
+
4
+ module VersacommerceAPI
5
+ class Cli < Thor
6
+ include Thor::Actions
7
+
8
+ class ConfigFileError < StandardError
9
+ end
10
+
11
+ desc "list", "List all saved CONNECTIONs"
12
+ def list
13
+ available_connections.each do |c|
14
+ prefix = default?(c) ? " * " : " "
15
+ puts prefix + c
16
+ end
17
+ end
18
+
19
+ desc "add [CONNECTION]", "Create a config file for a connection named CONNECTION"
20
+ def add(connection)
21
+ file = config_file(connection)
22
+ if File.exist?(file)
23
+ raise ConfigFileError, "There is already a config file at #{file}"
24
+ else
25
+ config = {'protocol' => 'http'}
26
+ config['domain'] = ask("Domain (leave blank for #{connection}.versacommerce.de):")
27
+ config['domain'] = "#{connection}.versacommerce.de" if config['domain'].blank?
28
+ config['domain'] = "#{config['domain']}.versacommerce.de" unless config['domain'].match(/[.:]/)
29
+ puts "\nopen http://#{config['domain']}/admin/settings/apps in your browser to get API credentials\n"
30
+ config['api_key'] = ask("API key :")
31
+ config['password'] = ask("Password:")
32
+ create_file(file, config.to_yaml)
33
+ end
34
+ if available_connections.one?
35
+ default(connection)
36
+ end
37
+ end
38
+
39
+ desc "remove [CONNECTION]", "Remove the config file for CONNECTION"
40
+ def remove(connection)
41
+ file = config_file(connection)
42
+ if File.exist?(file)
43
+ remove_file(default_symlink) if default?(connection)
44
+ remove_file(file)
45
+ else
46
+ config_file_not_found_error(file)
47
+ end
48
+ end
49
+
50
+ desc "edit [CONNECTION]", "Open the config file for CONNECTION with your default editor"
51
+ def edit(connection=nil)
52
+ file = config_file(connection)
53
+ if File.exist?(file)
54
+ if ENV['EDITOR'].present?
55
+ system(ENV['EDITOR'], file)
56
+ else
57
+ puts "Please set an editor in the EDITOR environment variable"
58
+ end
59
+ else
60
+ config_file_not_found_error(file)
61
+ end
62
+ end
63
+
64
+ desc "show [CONNECTION]", "Show the location and contents of the CONNECTION's config file"
65
+ def show(connection=nil)
66
+ connection ||= default_connection
67
+ file = config_file(connection)
68
+ if File.exist?(file)
69
+ puts file
70
+ puts `cat #{file}`
71
+ else
72
+ config_file_not_found_error(file)
73
+ end
74
+ end
75
+
76
+ desc "default [CONNECTION]", "Show or set the default connection"
77
+ def default(connection=nil)
78
+ if connection
79
+ target = config_file(connection)
80
+ if File.exist?(target)
81
+ remove_file(default_symlink)
82
+ `ln -s #{target} #{default_symlink}`
83
+ else
84
+ config_file_not_found_error(target)
85
+ end
86
+ end
87
+ if File.exist?(default_symlink)
88
+ puts "Default connection is #{default_connection}"
89
+ else
90
+ puts "There is no default connection set"
91
+ end
92
+ end
93
+
94
+ desc "console [CONNECTION]", "Startup an API console for CONNECTION"
95
+ def console(connection=nil)
96
+ file = config_file(connection)
97
+
98
+ config = YAML.load(File.read(file))
99
+ puts ""
100
+ puts "--> Starting interactive API console for #{config['domain']} - #{file}"
101
+ puts ""
102
+ VersacommerceAPI::Base.site = site_from_config(config)
103
+
104
+ require 'irb'
105
+ require 'irb/completion'
106
+ ARGV.clear
107
+ IRB.start
108
+ end
109
+
110
+ tasks.keys.abbrev.each do |shortcut, command|
111
+ map shortcut => command.to_sym
112
+ end
113
+
114
+ private
115
+
116
+ def shop_config_dir
117
+ @shop_config_dir ||= File.join(ENV['HOME'], '.versacommerce', 'shops')
118
+ end
119
+
120
+ def default_symlink
121
+ @default_symlink ||= File.join(shop_config_dir, 'default')
122
+ end
123
+
124
+ def config_file(connection)
125
+ if connection
126
+ File.join(shop_config_dir, "#{connection}.yml")
127
+ else
128
+ default_symlink
129
+ end
130
+ end
131
+
132
+ def site_from_config(config)
133
+ protocol = config['protocol'] || 'https'
134
+ api_key = config['api_key']
135
+ password = config['password']
136
+ domain = config['domain']
137
+
138
+ VersacommerceAPI::Base.site = "#{protocol}://#{api_key}:#{password}@#{domain}/api"
139
+ end
140
+
141
+ def available_connections
142
+ @available_connections ||= begin
143
+ pattern = File.join(shop_config_dir, "*.yml")
144
+ Dir.glob(pattern).map { |f| File.basename(f, ".yml") }
145
+ end
146
+ end
147
+
148
+ def default_connection_target
149
+ @default_connection_target ||= File.readlink(default_symlink)
150
+ end
151
+
152
+ def default_connection
153
+ @default_connection ||= File.basename(default_connection_target, ".yml")
154
+ end
155
+
156
+ def default?(connection)
157
+ default_connection == connection
158
+ end
159
+
160
+ def config_file_not_found_error(filename)
161
+ raise ConfigFileError, "Could not find config file at #{filename}"
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,9 @@
1
+ module VersacommerceAPI
2
+
3
+ module Countable
4
+ def count(options = {})
5
+ Integer(get(:count, options))
6
+ end
7
+ end
8
+
9
+ end
@@ -0,0 +1,2 @@
1
+ require 'versacommerce_api/resources/base'
2
+ Dir.glob "#{File.dirname(__FILE__)}/resources/*", &method(:require)
@@ -0,0 +1,42 @@
1
+ module VersacommerceAPI
2
+
3
+ class Base < ActiveResource::Base
4
+ extend Countable
5
+ self.format = :xml
6
+ self.headers['User-Agent'] = ["VersacommerceAPI/#{VersacommerceAPI::VERSION}",
7
+ "ActiveResource/#{ActiveResource::VERSION::STRING}",
8
+ "Ruby/#{RUBY_VERSION}"].join(' ')
9
+
10
+ def self.all
11
+ self.find(:all)
12
+ end
13
+
14
+ def self.root!
15
+ self.prefix = "/"
16
+ end
17
+
18
+ class << self
19
+ def headers
20
+ if defined?(@headers)
21
+ @headers
22
+ elsif superclass != Object && superclass.headers
23
+ superclass.headers
24
+ else
25
+ @headers ||= {}
26
+ end
27
+ end
28
+
29
+ def activate_session(session)
30
+ self.site = session.site
31
+ self.headers.merge!('X-Versacommerce-API-Token' => session.token)
32
+ end
33
+
34
+ def clear_session
35
+ self.site = nil
36
+ self.headers.delete('X-Versacommerce-API-Token')
37
+ end
38
+ end
39
+
40
+ end
41
+
42
+ end
@@ -0,0 +1,9 @@
1
+ module VersacommerceAPI
2
+
3
+ class BillingAddres < Base
4
+ def fullname
5
+ [firstname, lastname].delete_if(&:blank?).compact * " "
6
+ end
7
+ end
8
+
9
+ end
@@ -0,0 +1,6 @@
1
+ module VersacommerceAPI
2
+
3
+ class Collection < Base
4
+ end
5
+
6
+ end
@@ -0,0 +1,6 @@
1
+ module VersacommerceAPI
2
+
3
+ class Customer < Base
4
+ end
5
+
6
+ end
@@ -0,0 +1,9 @@
1
+ module VersacommerceAPI
2
+
3
+ class Item < Base
4
+ def product
5
+ Product.find product_id
6
+ end
7
+ end
8
+
9
+ end
@@ -0,0 +1,15 @@
1
+ module VersacommerceAPI
2
+
3
+ class Link < Base
4
+
5
+ def linked
6
+ resource.find(self.linkable_id) if self.linkable_id && resource
7
+ end
8
+
9
+ def resource
10
+ "VersaCommerceShopApi::#{self.linkable_type}".constantize rescue nil
11
+ end
12
+
13
+ end
14
+
15
+ end
@@ -0,0 +1,11 @@
1
+ module VersacommerceAPI
2
+
3
+ class Linklist < Base
4
+ include Associatable
5
+
6
+ def links
7
+ associated_resource "link"
8
+ end
9
+ end
10
+
11
+ end
@@ -0,0 +1,22 @@
1
+ module VersacommerceAPI
2
+
3
+ # An order comes by default with three associations: shipping_address, billing_address and customer.
4
+ # because we already know the amount of data. if you want to include order items use the :include option like:
5
+ # VersaCommerceShopApi::Order.find(:all, :params => {:include => 'items', :limit => 10})
6
+ class Order < Base
7
+ include Associatable
8
+
9
+ def payments
10
+ associated_resource "payment"
11
+ end
12
+
13
+ def shipping_address
14
+ associated_resource "shipping_address", false
15
+ end
16
+
17
+ def billing_address
18
+ associated_resource "billing_address", false
19
+ end
20
+ end
21
+
22
+ end
@@ -0,0 +1,6 @@
1
+ module VersacommerceAPI
2
+
3
+ class Page < Base
4
+ end
5
+
6
+ end
@@ -0,0 +1,6 @@
1
+ module VersacommerceAPI
2
+
3
+ class Payment < Base
4
+ end
5
+
6
+ end
@@ -0,0 +1,55 @@
1
+ module VersacommerceAPI
2
+
3
+ class Product < Base
4
+ include Associatable
5
+
6
+ # compute the price range
7
+ def price_range
8
+ prices = variants.collect(&:price)
9
+ format = "%0.2f"
10
+ if prices.min != prices.max
11
+ "#{format % prices.min} - #{format % prices.max}"
12
+ else
13
+ format % prices.min
14
+ end
15
+ end
16
+
17
+ def available
18
+ amount_available(0) && active
19
+ end
20
+
21
+ def amount_available(amount)
22
+ if considers_stock
23
+ stock >= amount
24
+ else
25
+ true
26
+ end
27
+ end
28
+
29
+ def is_variant
30
+ !product_id.nil?
31
+ end
32
+
33
+ def properties
34
+ associated_resource "property"
35
+ end
36
+
37
+ def product_images
38
+ associated_resource "product_image"
39
+ end
40
+
41
+ def variants
42
+ associated_resource "variant"
43
+ end
44
+
45
+ def tags
46
+ return [] if self.respond_to?("tag_list") && self.send("tag_list").blank?
47
+ tag_list.split(",").map(&:strip)
48
+ end
49
+
50
+ def featured_image
51
+ ProductImage.new(:src => featured_image_url)
52
+ end
53
+ end
54
+
55
+ end
@@ -0,0 +1,67 @@
1
+ module VersacommerceAPI
2
+
3
+ class ProductImage < Base
4
+
5
+ def initialize(attributes)
6
+ super
7
+ if self.attributes['file']
8
+ file = self.attributes['file']
9
+ data = file.read
10
+ type, suffix = file.content_type.split("/")
11
+ raise ArgumentError.new("file must be an image") unless ((type == "image") && %w(jpeg jpg gif png).include?(suffix))
12
+ filename = "upload_file.#{suffix}"
13
+ upload_image(data, filename)
14
+ self.attributes.delete 'file'
15
+ end
16
+ end
17
+
18
+ def pico
19
+ generate_resized_url(original, :resize, '16x16')
20
+ end
21
+
22
+ def icon
23
+ generate_resized_url(original, :resize, '32x32')
24
+ end
25
+
26
+ def thumb
27
+ generate_resized_url(original, :resize, '50x50')
28
+ end
29
+
30
+ def small
31
+ generate_resized_url(original, :resize, '100x100')
32
+ end
33
+
34
+ def medium
35
+ generate_resized_url(original, :resize, '240x240')
36
+ end
37
+
38
+ def large
39
+ generate_resized_url(original, :resize, '480x480')
40
+ end
41
+
42
+ def xlarge
43
+ generate_resized_url(original, :resize, '960x960')
44
+ end
45
+
46
+ def standard
47
+ generate_resized_url(original, :resize, '1024x1024')
48
+ end
49
+
50
+ def original
51
+ "http://images.versacommerce.net/++/#{src.gsub("http://", "")}"
52
+ end
53
+
54
+ def upload_image(data, filename = nil)
55
+ attributes['image_data'] = Base64.encode64(data)
56
+ attributes['filename'] = filename unless filename.nil?
57
+ end
58
+
59
+ private
60
+
61
+ def generate_resized_url(url, command, value)
62
+ refit_url = url.gsub("\/++\/", "\/#{command}=#{value}\/++\/")
63
+ end
64
+
65
+ end
66
+
67
+ end