crystal_api 0.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 (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