versacommerce_api 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/.ruby-version +1 -0
- data/CHANGELOG +2 -0
- data/CONTRIBUTORS +0 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +59 -0
- data/LICENSE.txt +22 -0
- data/README.md +200 -0
- data/RELEASING +16 -0
- data/Rakefile +1 -0
- data/bin/versacommerce +4 -0
- data/lib/active_resource/base_ext.rb +22 -0
- data/lib/active_resource/connection_ext.rb +31 -0
- data/lib/active_resource/detailed_log_subscriber.rb +19 -0
- data/lib/active_resource/disable_prefix_check.rb +8 -0
- data/lib/active_resource/json_errors.rb +21 -0
- data/lib/versacommerce_api.rb +21 -0
- data/lib/versacommerce_api/associatable.rb +25 -0
- data/lib/versacommerce_api/cli.rb +164 -0
- data/lib/versacommerce_api/countable.rb +9 -0
- data/lib/versacommerce_api/resources.rb +2 -0
- data/lib/versacommerce_api/resources/base.rb +42 -0
- data/lib/versacommerce_api/resources/billing_addres.rb +9 -0
- data/lib/versacommerce_api/resources/collection.rb +6 -0
- data/lib/versacommerce_api/resources/customer.rb +6 -0
- data/lib/versacommerce_api/resources/item.rb +9 -0
- data/lib/versacommerce_api/resources/link.rb +15 -0
- data/lib/versacommerce_api/resources/linklist.rb +11 -0
- data/lib/versacommerce_api/resources/order.rb +22 -0
- data/lib/versacommerce_api/resources/page.rb +6 -0
- data/lib/versacommerce_api/resources/payment.rb +6 -0
- data/lib/versacommerce_api/resources/product.rb +55 -0
- data/lib/versacommerce_api/resources/product_image.rb +67 -0
- data/lib/versacommerce_api/resources/property.rb +6 -0
- data/lib/versacommerce_api/resources/shipment.rb +11 -0
- data/lib/versacommerce_api/resources/shipping_addres.rb +9 -0
- data/lib/versacommerce_api/resources/shop.rb +10 -0
- data/lib/versacommerce_api/resources/variant.rb +7 -0
- data/lib/versacommerce_api/session.rb +88 -0
- data/lib/versacommerce_api/version.rb +3 -0
- data/spec/resources/order_spec.rb +71 -0
- data/spec/resources/product_spec.rb +73 -0
- data/spec/spec_helper.rb +18 -0
- data/versacommerce_api.gemspec +37 -0
- 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,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,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,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
|