crystal_api 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +4 -0
  5. data/Guardfile +7 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +50 -0
  8. data/Rakefile +1 -0
  9. data/crystal_api.gemspec +34 -0
  10. data/lib/crystal_api.rb +51 -0
  11. data/lib/crystal_api/attributes.rb +210 -0
  12. data/lib/crystal_api/category.rb +28 -0
  13. data/lib/crystal_api/descriptor.rb +11 -0
  14. data/lib/crystal_api/error_response.rb +17 -0
  15. data/lib/crystal_api/errors.rb +8 -0
  16. data/lib/crystal_api/hmac_request_signing.rb +40 -0
  17. data/lib/crystal_api/market_prices.rb +11 -0
  18. data/lib/crystal_api/message_verifier.rb +26 -0
  19. data/lib/crystal_api/money.rb +8 -0
  20. data/lib/crystal_api/paginated_collection.rb +13 -0
  21. data/lib/crystal_api/photo.rb +10 -0
  22. data/lib/crystal_api/product.rb +55 -0
  23. data/lib/crystal_api/product_descriptor.rb +10 -0
  24. data/lib/crystal_api/received_webhook_parser.rb +17 -0
  25. data/lib/crystal_api/report.rb +17 -0
  26. data/lib/crystal_api/store.rb +10 -0
  27. data/lib/crystal_api/store_endpoint.rb +82 -0
  28. data/lib/crystal_api/store_prefs.rb +54 -0
  29. data/lib/crystal_api/url.rb +9 -0
  30. data/lib/crystal_api/variant.rb +28 -0
  31. data/lib/crystal_api/variant_descriptor.rb +10 -0
  32. data/lib/crystal_api/version.rb +3 -0
  33. data/lib/crystal_api/webhook.rb +15 -0
  34. data/lib/crystal_api/webhook_envelope.rb +12 -0
  35. data/lib/crystal_api/webhook_registration.rb +34 -0
  36. data/lib/crystal_api/webhook_verifier.rb +20 -0
  37. data/spec/cassettes/CrystalApi_StoreEndpoint/_get/prefs/store/makes_the_request_to_the_endpoint.yml +67 -0
  38. data/spec/cassettes/CrystalApi_StoreEndpoint/_get/prefs/store/parses_the_returned_a_store_pref.yml +67 -0
  39. data/spec/cassettes/CrystalApi_StoreEndpoint/_get/prefs/store/returns_a_store_pref_instance.yml +67 -0
  40. data/spec/crystal_api/attributes_spec.rb +7 -0
  41. data/spec/crystal_api/category_spec.rb +112 -0
  42. data/spec/crystal_api/money_spec.rb +10 -0
  43. data/spec/crystal_api/paginated_collection_spec.rb +31 -0
  44. data/spec/crystal_api/photo_spec.rb +41 -0
  45. data/spec/crystal_api/product_spec.rb +209 -0
  46. data/spec/crystal_api/received_webhook_parser_spec.rb +23 -0
  47. data/spec/crystal_api/report_spec.rb +33 -0
  48. data/spec/crystal_api/store_endpoint_spec.rb +37 -0
  49. data/spec/crystal_api/store_prefs_spec.rb +119 -0
  50. data/spec/crystal_api/store_spec.rb +17 -0
  51. data/spec/crystal_api/variant_descriptor_spec.rb +16 -0
  52. data/spec/crystal_api/variant_spec.rb +85 -0
  53. data/spec/crystal_api/webhook_envelope_spec.rb +45 -0
  54. data/spec/crystal_api/webhook_registration_spec.rb +129 -0
  55. data/spec/crystal_api/webhook_spec.rb +54 -0
  56. data/spec/spec_helper.rb +25 -0
  57. data/spec/support/attribute_examples.rb +77 -0
  58. data/spec/support/vcr.rb +7 -0
  59. metadata +305 -0
@@ -0,0 +1,11 @@
1
+ module CrystalApi
2
+ class Descriptor
3
+ include Attributes
4
+
5
+ root_element :descriptor
6
+
7
+ boolean_attribute :is_enable_filter
8
+ string_attribute :name
9
+ array_attribute :options, :string
10
+ end
11
+ end
@@ -0,0 +1,17 @@
1
+ module CrystalApi
2
+ class ErrorResponse
3
+
4
+ attr_reader :json
5
+ def initialize(json)
6
+ @json = json
7
+ end
8
+
9
+ def error
10
+ json['error']
11
+ end
12
+
13
+ def description
14
+ json['description']
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,8 @@
1
+ module CrystalApi
2
+ class Error < ::StandardError
3
+ end
4
+
5
+ class CannotParseJson < Error
6
+ end
7
+ end
8
+
@@ -0,0 +1,40 @@
1
+ require 'base64'
2
+ require 'openssl'
3
+ require 'cgi'
4
+
5
+ module CrystalApi
6
+ module HmacRequestSigning
7
+ def inject_signature_header(headers, signature)
8
+ headers['X-Hmac-Sha256'] = signature
9
+ headers
10
+ end
11
+
12
+ def request_signature(request)
13
+ request.headers['X_HMAC_SHA256']
14
+ end
15
+
16
+ def hmac_sha256(key, message)
17
+ digest = OpenSSL::HMAC.digest('sha256', key, message)
18
+ # chomp -- the base64 encoded version will have a newline at the end
19
+ Base64.encode64(digest).chomp
20
+ end
21
+
22
+ def sign_params(verb, host, path, secret_key, params)
23
+ hmac_sha256(secret_key, serialized_params(verb, host, path, params))
24
+ end
25
+
26
+ def serialized_params(verb, host, path, params)
27
+ [ verb.to_s.upcase,
28
+ host,
29
+ path,
30
+ canonicalize_params(params) ].join("\n")
31
+ end
32
+
33
+ def canonicalize_params(params)
34
+ # Make sure we have string keys, otherwise the sort does not work
35
+ params.keys.sort.map do |key|
36
+ [CGI.escape(key.to_s), CGI.escape(params[key].to_s)].join('=')
37
+ end.join('&')
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,11 @@
1
+ module CrystalApi
2
+ class MarketPrices
3
+ include Attributes
4
+
5
+ root_element :market_prices
6
+
7
+ object_attribute :high_market_price
8
+ object_attribute :low_market_price
9
+ object_attribute :market_price
10
+ end
11
+ end
@@ -0,0 +1,26 @@
1
+ module CrystalApi
2
+ class MessageVerifier
3
+ include HmacRequestSigning
4
+
5
+ attr_reader :shared_secret, :request
6
+
7
+ def initialize(shared_secret, request)
8
+ @shared_secret = shared_secret
9
+ @request = request
10
+ end
11
+
12
+ def verified?
13
+ request_signature(request) == expected_signature
14
+ end
15
+
16
+ def expected_signature
17
+ rack_request = Rack::Request.new(request.env)
18
+
19
+ sign_params(rack_request.request_method,
20
+ rack_request.host,
21
+ rack_request.path,
22
+ shared_secret,
23
+ rack_request.params)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,8 @@
1
+ module CrystalApi
2
+ class Money < ::Money
3
+ def self.from_json(hash)
4
+ attrs = hash.fetch('money', {})
5
+ ::Money.new(attrs.fetch('cents', 0), attrs.fetch('currency', 'USD'))
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,13 @@
1
+ module CrystalApi
2
+ class PaginatedCollection
3
+ include Attributes
4
+
5
+ root_element :paginated_collection
6
+
7
+ integer_attribute :total_entries
8
+ integer_attribute :total_pages
9
+ integer_attribute :previous_page
10
+ integer_attribute :next_page
11
+ array_attribute :entries
12
+ end
13
+ end
@@ -0,0 +1,10 @@
1
+ module CrystalApi
2
+ class Photo
3
+ include Attributes
4
+
5
+ root_element :photo
6
+
7
+ boolean_attribute :is_default
8
+ hash_attribute :urls, :url
9
+ end
10
+ end
@@ -0,0 +1,55 @@
1
+ module CrystalApi
2
+ class Product
3
+ include Attributes
4
+
5
+ root_element :product
6
+
7
+ integer_attribute :id
8
+ integer_attribute :catalog_id
9
+
10
+ string_attribute :name
11
+ string_attribute :seoname
12
+ string_attribute :description
13
+
14
+ decimal_attribute :weight
15
+
16
+ integer_attribute :qty
17
+ integer_attribute :wtb_qty
18
+ integer_attribute :max_qty
19
+
20
+ boolean_attribute :is_buying
21
+ boolean_attribute :is_selling
22
+ boolean_attribute :is_domestic_only
23
+ boolean_attribute :is_preorder
24
+
25
+ object_attribute :msrp
26
+ object_attribute :base_sell_price
27
+ object_attribute :base_buy_price
28
+ object_attribute :min_sell_price
29
+ object_attribute :max_sell_price
30
+ object_attribute :min_buy_price
31
+ object_attribute :max_buy_price
32
+
33
+ datetime_attribute :updated_at
34
+ date_attribute :available_on
35
+
36
+ object_attribute :default_variant
37
+
38
+ array_attribute :related_tag_list, :string
39
+ array_attribute :tag_list, :string
40
+
41
+ array_attribute :variant_dimensions, :string
42
+
43
+ array_attribute :photos, :object
44
+ array_attribute :descriptors, :object
45
+
46
+ hash_attribute :catalog_links, :url
47
+ hash_attribute :buylist_links, :url
48
+
49
+ embedded_attribute :category
50
+
51
+ embedded_attribute :variants, :array
52
+
53
+ embedded_attribute :market_prices
54
+ end
55
+ end
@@ -0,0 +1,10 @@
1
+ module CrystalApi
2
+ class ProductDescriptor
3
+ include Attributes
4
+
5
+ root_element :product_descriptor
6
+
7
+ string_attribute :name
8
+ string_attribute :value
9
+ end
10
+ end
@@ -0,0 +1,17 @@
1
+ module CrystalApi
2
+ class ReceivedWebhookParser
3
+ attr_reader :raw_post
4
+
5
+ def initialize(raw_post)
6
+ @raw_post = raw_post
7
+ end
8
+
9
+ def webhook
10
+ WebhookEnvelope.from_json(json)
11
+ end
12
+
13
+ def json
14
+ @json ||= MultiJson.load(raw_post)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ module CrystalApi
2
+ class Report
3
+ include Attributes
4
+
5
+ root_element :report
6
+
7
+ integer_attribute :id
8
+ string_attribute :status
9
+ string_attribute :report_type
10
+
11
+ datetime_attribute :created_at
12
+ datetime_attribute :expires_at
13
+ datetime_attribute :updated_at
14
+
15
+ hash_attribute :_links, :url
16
+ end
17
+ end
@@ -0,0 +1,10 @@
1
+ module CrystalApi
2
+ class Store
3
+ include Attributes
4
+
5
+ root_element :store
6
+
7
+ string_attribute :name
8
+ string_attribute :database_name
9
+ end
10
+ end
@@ -0,0 +1,82 @@
1
+ require 'httparty'
2
+ require 'values'
3
+ require 'csv'
4
+
5
+ module CrystalApi
6
+ class StoreEndpoint
7
+ attr_reader :base_url, :token
8
+
9
+ class Response < Value.new(:success, :parsed, :raw, :json)
10
+ alias_method :to_hash, :json
11
+ alias_method :success?, :success
12
+
13
+ def to_s
14
+ "<CrystalApi::StoreEndpoint::Response parsed:#{parsed.class.name} raw.length:#{raw.length}>"
15
+ end
16
+ end
17
+
18
+ def initialize(args = {})
19
+ @base_url = args[:base_url]
20
+ @token = args[:token]
21
+ end
22
+
23
+ def headers
24
+ {
25
+ "Authorization" => "OAuth #{token}",
26
+ "Accept" => "application/json",
27
+ "Content-Type" => "application/json",
28
+ "X-Requested-With" => "XMLHttpRequest"
29
+ }
30
+ end
31
+
32
+ [:get, :delete, :post, :put].each do |method|
33
+ define_method(method) do |*args|
34
+ options = {:headers => headers}
35
+ if args.length == 2
36
+ options[:body] = args[1].to_json
37
+ end
38
+
39
+ path = args[0]
40
+ url = base_url + path
41
+
42
+ raw = HTTParty.public_send(method, url, options)
43
+ wrap_response(raw)
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def wrap_response(raw)
50
+ if raw.content_type =~ /application\/json/
51
+ json = JSON.parse(raw.body)
52
+ if raw.code == 200
53
+ parsed = parse_response_body(json)
54
+ else
55
+ parsed = error_response(json)
56
+ end
57
+ elsif raw.content_type =~ /text\/csv/
58
+ parsed = CSV.parse(raw.body, :headers => true)
59
+ end
60
+
61
+ Response.new(raw.code == 200, parsed, raw, json)
62
+ end
63
+
64
+ def error_response(json)
65
+ ErrorResponse.new(json)
66
+ end
67
+
68
+ def parse_response_body(json)
69
+ if json.is_a?(Array)
70
+ json.map {|obj| parse(obj)}
71
+ else
72
+ parse(json)
73
+ end
74
+ rescue JSON::ParserError
75
+ nil
76
+ end
77
+
78
+ def parse(object)
79
+ CrystalApi.from_json(object)
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,54 @@
1
+ module CrystalApi
2
+ class StorePrefs
3
+ include Attributes
4
+
5
+ root_element :store_prefs
6
+
7
+ string_attribute :city
8
+ string_attribute :hostname
9
+ string_attribute :return_blurb
10
+ string_attribute :time_zone
11
+ string_attribute :customer_service_phone
12
+ string_attribute :zip
13
+ string_attribute :sales_tax
14
+ string_attribute :cancel
15
+ string_attribute :default_currency_code
16
+ string_attribute :default_locale
17
+ string_attribute :privacy_blurb
18
+ string_attribute :store_credit_multiplier
19
+ string_attribute :buylist_cancel_days
20
+ string_attribute :checkout_special_instructions
21
+ string_attribute :default_exchange_rate
22
+ string_attribute :feedback_timespan
23
+ string_attribute :home_redirect
24
+ string_attribute :phone
25
+ string_attribute :state
26
+ string_attribute :address
27
+ string_attribute :payment
28
+ string_attribute :contact_name
29
+ string_attribute :name
30
+ string_attribute :watermark_photo
31
+ string_attribute :country
32
+ string_attribute :contact_email
33
+ string_attribute :buylist_reminder_days
34
+ string_attribute :enable_order_notifications
35
+ string_attribute :enable_buy_order_notifications
36
+ string_attribute :buylist_email
37
+ string_attribute :satisfaction_blurb
38
+
39
+ boolean_attribute :display_customer_service_phone
40
+ boolean_attribute :hide_bank_transfer_details_in_emails
41
+ boolean_attribute :enable_manual_credit_card_capture
42
+ boolean_attribute :enable_buylist
43
+ boolean_attribute :enable_bcc_buylist_confirmation_emails
44
+ boolean_attribute :enable_tax_on_shipping
45
+ boolean_attribute :enable_sort_by_price
46
+ boolean_attribute :enable_referafriend
47
+ boolean_attribute :enable_account_approval
48
+ boolean_attribute :enable_watermarking_of_product_photos
49
+ boolean_attribute :enable_invoice_logo
50
+ boolean_attribute :enable_frontend_auto_translate
51
+
52
+ object_attribute :logo_photo
53
+ end
54
+ end
@@ -0,0 +1,9 @@
1
+ module CrystalApi
2
+ class Url
3
+ include Attributes
4
+
5
+ root_element :url
6
+
7
+ string_attribute :href
8
+ end
9
+ end
@@ -0,0 +1,28 @@
1
+ module CrystalApi
2
+ class Variant
3
+ include Attributes
4
+
5
+ root_element :variant
6
+
7
+ object_attribute :sell_price
8
+ object_attribute :buy_price
9
+ object_attribute :store_credit_buy_price
10
+
11
+ integer_attribute :id
12
+ integer_attribute :product_id
13
+ integer_attribute :product_catalog_id
14
+ integer_attribute :qty
15
+ integer_attribute :actual_qty
16
+ integer_attribute :inventory_qty
17
+ integer_attribute :wtb_qty
18
+
19
+ string_attribute :catalog_id
20
+ string_attribute :product_name
21
+ string_attribute :category_name
22
+
23
+ boolean_attribute :is_default
24
+ boolean_attribute :is_infinite_qty
25
+
26
+ array_attribute :descriptors, :object
27
+ end
28
+ end