mindbody-api 1.0.1.alpha

Sign up to get free protection for your applications and to get access to all the features.
Files changed (96) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +20 -0
  3. data/.travis.yml +5 -0
  4. data/CONTRIBUTING.md +16 -0
  5. data/Gemfile +12 -0
  6. data/Guardfile +6 -0
  7. data/LICENSE.md +22 -0
  8. data/README.md +79 -0
  9. data/Rakefile +52 -0
  10. data/lib/mindbody-api.rb +35 -0
  11. data/lib/mindbody-api/api_status.rb +109 -0
  12. data/lib/mindbody-api/client.rb +39 -0
  13. data/lib/mindbody-api/ext/savon_ext.rb +53 -0
  14. data/lib/mindbody-api/models.rb +34 -0
  15. data/lib/mindbody-api/models/appointment.rb +22 -0
  16. data/lib/mindbody-api/models/appointment_status.rb +13 -0
  17. data/lib/mindbody-api/models/availability.rb +11 -0
  18. data/lib/mindbody-api/models/class.rb +31 -0
  19. data/lib/mindbody-api/models/class_description.rb +14 -0
  20. data/lib/mindbody-api/models/class_schedule.rb +26 -0
  21. data/lib/mindbody-api/models/client.rb +32 -0
  22. data/lib/mindbody-api/models/client_service.rb +16 -0
  23. data/lib/mindbody-api/models/course.rb +16 -0
  24. data/lib/mindbody-api/models/level.rb +8 -0
  25. data/lib/mindbody-api/models/location.rb +38 -0
  26. data/lib/mindbody-api/models/payment.rb +11 -0
  27. data/lib/mindbody-api/models/program.rb +9 -0
  28. data/lib/mindbody-api/models/resource.rb +8 -0
  29. data/lib/mindbody-api/models/sale.rb +10 -0
  30. data/lib/mindbody-api/models/sale_item.rb +14 -0
  31. data/lib/mindbody-api/models/schedule_type.rb +13 -0
  32. data/lib/mindbody-api/models/semester.rb +9 -0
  33. data/lib/mindbody-api/models/service.rb +13 -0
  34. data/lib/mindbody-api/models/session_type.rb +8 -0
  35. data/lib/mindbody-api/models/site.rb +23 -0
  36. data/lib/mindbody-api/models/staff.rb +32 -0
  37. data/lib/mindbody-api/models/unavailability.rb +7 -0
  38. data/lib/mindbody-api/models/visit.rb +19 -0
  39. data/lib/mindbody-api/response.rb +119 -0
  40. data/lib/mindbody-api/service.rb +39 -0
  41. data/lib/mindbody-api/services/appointment_service.rb +15 -0
  42. data/lib/mindbody-api/services/class_service.rb +12 -0
  43. data/lib/mindbody-api/services/client_service.rb +30 -0
  44. data/lib/mindbody-api/services/finder_service.rb +11 -0
  45. data/lib/mindbody-api/services/sale_service.rb +14 -0
  46. data/lib/mindbody-api/services/site_service.rb +15 -0
  47. data/lib/mindbody-api/services/staff_service.rb +12 -0
  48. data/lib/mindbody-api/version.rb +4 -0
  49. data/mindbody-api.gemspec +29 -0
  50. data/spec/api_status_spec.rb +110 -0
  51. data/spec/client_spec.rb +51 -0
  52. data/spec/fixtures/wsdl/geotrust.wsdl +156 -0
  53. data/spec/mindbody_spec.rb +79 -0
  54. data/spec/model_spec.rb +7 -0
  55. data/spec/models/appointment_spec.rb +18 -0
  56. data/spec/models/appointment_status_spec.rb +17 -0
  57. data/spec/models/availability.rb +8 -0
  58. data/spec/models/class_description_spec.rb +12 -0
  59. data/spec/models/class_schedule_spec.rb +24 -0
  60. data/spec/models/class_spec.rb +39 -0
  61. data/spec/models/client_service_spec.rb +13 -0
  62. data/spec/models/client_spec.rb +33 -0
  63. data/spec/models/course_spec.rb +14 -0
  64. data/spec/models/level_spec.rb +6 -0
  65. data/spec/models/location_spec.rb +34 -0
  66. data/spec/models/payment_spec.rb +9 -0
  67. data/spec/models/program_spec.rb +7 -0
  68. data/spec/models/resource_spec.rb +6 -0
  69. data/spec/models/sale_item_spec.rb +12 -0
  70. data/spec/models/sale_spec.rb +8 -0
  71. data/spec/models/schedule_type_spec.rb +17 -0
  72. data/spec/models/semester_spec.rb +7 -0
  73. data/spec/models/service_spec.rb +11 -0
  74. data/spec/models/session_type_spec.rb +6 -0
  75. data/spec/models/site_spec.rb +31 -0
  76. data/spec/models/staff_spec.rb +29 -0
  77. data/spec/models/unavailability.rb +5 -0
  78. data/spec/models/visit_spec.rb +16 -0
  79. data/spec/response_spec.rb +115 -0
  80. data/spec/service_spec.rb +106 -0
  81. data/spec/services/appointment_service_spec.rb +13 -0
  82. data/spec/services/class_service_spec.rb +10 -0
  83. data/spec/services/client_service_spec.rb +27 -0
  84. data/spec/services/finder_service_spec.rb +9 -0
  85. data/spec/services/sale_service_spec.rb +12 -0
  86. data/spec/services/site_service_spec.rb +13 -0
  87. data/spec/services/staff_service_spec.rb +10 -0
  88. data/spec/spec_helper.rb +18 -0
  89. data/wsdl/AppointmentService.wsdl +1084 -0
  90. data/wsdl/ClassService.wsdl +1656 -0
  91. data/wsdl/ClientService.wsdl +2295 -0
  92. data/wsdl/FinderService.wsdl +1427 -0
  93. data/wsdl/SaleService.wsdl +1678 -0
  94. data/wsdl/SiteService.wsdl +915 -0
  95. data/wsdl/StaffService.wsdl +939 -0
  96. metadata +277 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 177b54dfe5a26b025296433fc1feac2a0718c3b6
4
+ data.tar.gz: 8fa1c5cd465d3e65925bbb651c2ffe651543c5cd
5
+ SHA512:
6
+ metadata.gz: 0670b56e47509479b718e6381bbd09ee5781b9174b53588d487433acebd0dd11fe290190889f5363d20becc217885265bdffeaef11119d0d81aa30dcddb7bdd2
7
+ data.tar.gz: cfed01706925dcd97a82b9caa2fc19920167e390a75cad18763f48b4da15d6bd0c9c850b3cf807041d298c9b8739fde6cfa81f1b6df764d1829dc454d2783cfd
@@ -0,0 +1,20 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .rbenv-version
19
+ .ruby-version
20
+ .env
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - 2.1.0
@@ -0,0 +1,16 @@
1
+ # Contributing
2
+
3
+ ## Guidelines:
4
+
5
+ * All changes should be in a topic branch
6
+ * All changes should have appropriate test coverage
7
+ * 2 spaces (not tabs) for indention
8
+ * Travis builds must be green prior to merging
9
+
10
+ ## Steps:
11
+
12
+ 1. Fork it
13
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
14
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
15
+ 4. Push to the branch (`git push origin my-new-feature`)
16
+ 5. Create new Pull Request
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in mindbody-api.gemspec
4
+ gemspec
5
+
6
+ # For guard
7
+ gem 'rb-inotify', :require => false
8
+ gem 'rb-fsevent', :require => false
9
+ gem 'rb-fchange', :require => false
10
+
11
+ # To pick up the .env file
12
+ gem 'dotenv'
@@ -0,0 +1,6 @@
1
+ guard 'rspec' do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/mindbody-api/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { "spec" }
5
+ end
6
+
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Stafford Brunk
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,79 @@
1
+ # MindBody API
2
+
3
+ Provides a Ruby interface to the [MindBody API](http://www.mindbodyonline.com/api)
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'mindbody-api'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install mindbody-api
18
+
19
+ ## Configuration
20
+
21
+ You must set your MindBody source name, key, and site ids. These values are accessible via the [MindBody Partner Page](https://api.mindbodyonline.com/Home/LogIn)
22
+
23
+ The easiest way to set these is via three environment variables: `MINDBODY_SOURCE_NAME`, `MINDBODY_SOURCE_KEY`, and `MINDBODY_SITE_IDS`. `MINDBODY_SITE_IDS` can have any delimiter.
24
+
25
+ Alternatively, you may set them in an initializer:
26
+
27
+ MindBody.configure do |config|
28
+ config.site_ids = -99
29
+ config.source_key = 'abcd1234'
30
+ config.source_name = 'SuperFoo'
31
+ config.log_level = :info # Savon logging level. Default is :debug, options are [:debug, :info, :warn, :error, :fatal]
32
+ end
33
+
34
+ See http://savonrb.com/version2/globals.html for more information on the logging
35
+ setting.
36
+
37
+ ## Usage
38
+
39
+ `mindbody-api` is laid out into two distinct parts: [services](https://github.com/wingrunr21/mindbody-api/tree/master/lib/mindbody-api/services) and [models](https://github.com/wingrunr21/mindbody-api/tree/master/lib/mindbody-api/models)
40
+
41
+ ### Services
42
+
43
+ The services are direct mirrors of those documented by MindBody in their [API
44
+ documentation](https://api.mindbodyonline.com/Doc). Required data arguments for
45
+ each service call are defined as arguments for the Ruby methods. Optional
46
+ arguments should be passed as a string based hash. For example:
47
+
48
+ client_id = 1234
49
+ response = MindBody::Services::ClientService.get_client_visits(client_id, 'StartDate' => Date.today, 'EndDate' => Date.today)
50
+ result = response.result
51
+ visits = result[:visits]
52
+
53
+ If any of the service calls need to be passed in a list of something (eg `Int32`
54
+ or `Int64`) then the option needs to be specified like this:
55
+
56
+ MindBody::Services::StaffService.get_staff('StaffIDs' => {'long' => [123415123, 123123213]})
57
+
58
+ ### Models
59
+
60
+ When a request comes back from MindBody, the various data types are hydrated
61
+ into models that are defined in the `MindBody::Models` namespace.
62
+
63
+ ## Versioning
64
+
65
+ In theory the gem would track the version of the MindBody API. In practice their
66
+ version number does not increment in a way that would make sense for a rubygem.
67
+ So, the gem itself uses [semver](http://semver.org/) and makes available a
68
+ constant `API_VERSION` that stores the version of the MindBody API.
69
+
70
+ ## Roadmap
71
+
72
+ See the various [issues](https://github.com/wingrunr21/mindbody-api/issues?state=open)
73
+
74
+ ## Credits
75
+
76
+ This gem is written by [Stafford Brunk](https://github.com/wingrunr21)
77
+
78
+ [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/wingrunr21/mindbody-api/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
79
+
@@ -0,0 +1,52 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ Bundler::GemHelper.install_tasks
4
+ require 'rspec/core/rake_task'
5
+ require 'dotenv/tasks'
6
+
7
+ # Rake tasks from https://github.com/mojombo/rakegem/blob/master/Rakefile
8
+
9
+ # Helper Functions
10
+ def name
11
+ @name ||= Dir['*.gemspec'].first.split('.').first
12
+ end
13
+
14
+ def version
15
+ line = File.read("lib/#{name}/version.rb")[/^\s*VERSION\s*=\s*.*/]
16
+ line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
17
+ end
18
+
19
+ # Standard tasks
20
+ RSpec::Core::RakeTask.new(:spec)
21
+ task :test => :spec
22
+ task :default => :spec
23
+
24
+ require 'rdoc/task'
25
+ Rake::RDocTask.new do |rdoc|
26
+ rdoc.rdoc_dir = 'rdoc'
27
+ rdoc.title = "#{name} #{version}"
28
+ rdoc.rdoc_files.include('README*')
29
+ rdoc.rdoc_files.include('lib/**/*.rb')
30
+ end
31
+
32
+ desc "Open an irb session preloaded with this library"
33
+ task :console => :dotenv do
34
+ sh "pry -Ilib -r #{name}.rb"
35
+ end
36
+
37
+ namespace :wsdl do
38
+ desc "Update the cached MindBody API wsdls"
39
+ task :update do
40
+ require 'open-uri'
41
+
42
+ url = "https://api.mindbodyonline.com/0_5/%sService.asmx?wsdl"
43
+ services = %w{Appointment Class Client Finder Sale Site Staff}
44
+
45
+ services.each do |service|
46
+ wsdl = open(url % service).read
47
+ File.open("wsdl/#{service}Service.wsdl", 'w') do |f|
48
+ f.write wsdl
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,35 @@
1
+ require 'active_support/inflector'
2
+ require 'active_support/core_ext/array/extract_options'
3
+
4
+ require 'mindbody-api/version'
5
+ require 'mindbody-api/models'
6
+ require 'mindbody-api/service'
7
+ require 'mindbody-api/api_status'
8
+
9
+ module MindBody
10
+ class << self
11
+ def configure
12
+ yield(configuration) if block_given?
13
+ end
14
+
15
+ def configuration
16
+ @configuration ||= Config.new
17
+ end
18
+ end
19
+
20
+ class Config
21
+ attr_accessor :log_level, :open_timeout, :read_timeout, :source_name, :source_key, :site_ids
22
+
23
+ def initialize
24
+ @log_level = :debug
25
+ @source_name = ENV['MINDBODY_SOURCE_NAME'] || ''
26
+ @source_key = ENV['MINDBODY_SOURCE_KEY'] || ''
27
+ @site_ids = (ENV['MINDBODY_SITE_IDS'] || '').scan(/-?\d+/).map(&:to_i)
28
+ end
29
+
30
+ # Make sure site_ids is always an Array
31
+ def site_ids=(ids)
32
+ @site_ids = [*ids]
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,109 @@
1
+ module MindBody
2
+ module APIStatus
3
+ class Base
4
+ attr_accessor :message
5
+
6
+ def initialize(message = '')
7
+ @message = message
8
+ end
9
+
10
+ def self.status
11
+ :undefined_error
12
+ end
13
+
14
+ def status
15
+ self.class.status
16
+ end
17
+
18
+ def self.error_code
19
+ 9999
20
+ end
21
+
22
+ def error_code
23
+ self.class.error_code
24
+ end
25
+ end
26
+
27
+ def self.build_status(code, status, message = nil)
28
+ # Double underscores makes sure Foo Bar and FooBar get
29
+ # the same treatment
30
+ underscore_status = status.underscore.parameterize.underscore
31
+ klass_name = underscore_status.camelize
32
+
33
+ klass = if const_defined?(klass_name)
34
+ const_get(klass_name)
35
+ else
36
+ define_api_status_constant(klass_name, underscore_status, code)
37
+ end
38
+
39
+ klass.new(message)
40
+ end
41
+
42
+ def self.define_api_status_constant(name, status, code)
43
+ klass = const_set(name, Class.new(Base))
44
+ klass.send(:define_singleton_method, :status) {status.to_sym}
45
+ klass.send(:define_singleton_method, :error_code) {code}
46
+ klass
47
+ end
48
+
49
+ # MindBody API code defintions
50
+ # Commented out codes have not been verified via the API.
51
+ # MindBody's own code definitions are not accurate and
52
+ # they are not willing to provide the correct mappings
53
+ # publically at this time
54
+ STATUS_CODES = {
55
+ #101 => "Invalid Source Credentials",
56
+ 102 => "InvalidCredentials",
57
+ #103 => "Invalid Staff Credentials",
58
+ #104 => "Invalid User Credentials",
59
+ 200 => "Success",
60
+ #201 => "Something failed in your call",
61
+ #301 => "Client ID doesn't exist",
62
+ #302 => "Invalid SQL Format",
63
+ #303 => "Client Index doesn't exist",
64
+ #304 => "Client Index Value doesn't exist",
65
+ #305 => "Missing Required Client Fields",
66
+ #306 => "Invalid Class ID",
67
+ #308 => "Related Client ID doesn't exist",
68
+ #309 => "Client Custom Field doesn't exist",
69
+ #310 => "Client Custom Field Value has an incorrect data type",
70
+ #311 => "Rep ID doesn't exist",
71
+ #312 => "Staff ID doesn't exist",
72
+ #313 => "Missing required fields",
73
+ #314 => "Session/Appointment Type ID doesn't exist",
74
+ 315 => "InvalidParameters",
75
+ #316 => "Appointment ID doesn't exist",
76
+ #317 => "Availabiliy ID doesn't exist",
77
+ #318 => "Invalid Payment Info",
78
+ #319 => "Staff not available",
79
+ #320 => "Client Service ID not found",
80
+ #321 => "Class Schedule ID doesn't exist",
81
+ #322 => "ContactLog Creation/Update Error",
82
+ #323 => "Unable to find client or found duplicate",
83
+ #324 => "Add/Update Staff error",
84
+ #501 => "Scheduling restrictions failed",
85
+ #502 => "Client is suspended from booking",
86
+ #503 => "Client needs payment",
87
+ #504 => "Client already booked",
88
+ #600 => "Class full",
89
+ #601 => "Class requires payment",
90
+ #602 => "Class is outside of scheduling window",
91
+ #603 => "Client already booked at this time",
92
+ #604 => "Client doesn't meet the prerequisites",
93
+ #605 => "Class capacity exceeded exception",
94
+ #606 => "Client already on waitlist",
95
+ #607 => "Waitlist full exception",
96
+ #700 => "Invalid booking time",
97
+ #800 => "Permission Exception",
98
+ #801 => "Staff member may not receive tips",
99
+ #900 => "RetailObject Exception",
100
+ #901 => "RetailItemNotFound Exception",
101
+ #1000 => "Location ID doesn't exist",
102
+ #1001 => "Setting Not Enabled on site",
103
+ #1002 => "PromotionCode Exception",
104
+ #1003 => "Resource Exception",
105
+ #2000 => "Finder user does not exist",
106
+ #2001 => "Finder user already exists"
107
+ }
108
+ end
109
+ end
@@ -0,0 +1,39 @@
1
+ require 'mindbody-api/response'
2
+
3
+ module MindBody
4
+ module Services
5
+ class Client < Savon::Client
6
+
7
+ def call(operation_name, locals = {}, &block)
8
+ # Inject the auth params into the request and setup the
9
+ # correct request structure
10
+ @globals.open_timeout(MindBody.configuration.open_timeout)
11
+ @globals.read_timeout(MindBody.configuration.read_timeout)
12
+ @globals.log_level(MindBody.configuration.log_level)
13
+ locals = locals.has_key?(:message) ? locals[:message] : locals
14
+ locals = fixup_locals(locals)
15
+ params = {:message => {'Request' => auth_params.merge(locals)}}
16
+
17
+ # Run the request
18
+ response = super(operation_name, params, &block)
19
+ Response.new(response)
20
+ end
21
+
22
+ private
23
+ def auth_params
24
+ {'SourceCredentials'=>{'SourceName'=>MindBody.configuration.source_name,
25
+ 'Password'=>MindBody.configuration.source_key,
26
+ 'SiteIDs'=>{'int'=>MindBody.configuration.site_ids}}}
27
+ end
28
+
29
+ def fixup_locals(locals)
30
+ # TODO this needs fixed to support various list types
31
+ locals.each_pair do |key, value|
32
+ if value.is_a? Array
33
+ locals[key] = {'int' => value}
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,53 @@
1
+ module MindBody
2
+ module SavonExt
3
+ module Service
4
+ private
5
+ # Defines a class-level SOAP operation.
6
+ def define_class_operation(operation, *options)
7
+ options = options.extract_options!
8
+ params = prepare_params(options)
9
+
10
+ class_operation_module.module_eval <<-RUBY_EVAL, __FILE__, __LINE__+1
11
+ def #{operation.to_s.snakecase}(#{params.join(',')})
12
+ req_hash = {
13
+ #{params.select{|p| p != 'locals = {}'}
14
+ .map{|p| "'#{params_key(p)}' => #{p}"}
15
+ .join(',')}
16
+ }
17
+ locals ||= {}
18
+ client.call #{operation.inspect}, :message => locals.merge(req_hash)
19
+ end
20
+ RUBY_EVAL
21
+ end
22
+
23
+ # Defines an instance-level SOAP operation.
24
+ # Defers to the class-level operation
25
+ def define_instance_operation(operation, *options)
26
+ options = options.extract_options!
27
+ params = prepare_params(options)
28
+
29
+ instance_operation_module.module_eval <<-RUBY_EVAL, __FILE__, __LINE__+1
30
+ def #{operation.to_s.snakecase}(#{params.join(',')})
31
+ self.class.#{operation.to_s.snakecase} #{params.join(',').chomp(' = {}')}
32
+ end
33
+ RUBY_EVAL
34
+ end
35
+
36
+ # Builds the array of params the
37
+ # generated method will utilize
38
+ def prepare_params(options)
39
+ params = options[:required] || []
40
+ params = params.map {|p| p.to_s.snakecase}
41
+ params.push 'locals = {}' if options[:locals].nil? || options[:locals]
42
+ params
43
+ end
44
+
45
+ # Maps the snakecase key to the
46
+ # correct MindBody upper case
47
+ def params_key(k)
48
+ key = k.to_s.camelize
49
+ key.gsub('Id', 'ID')
50
+ end
51
+ end
52
+ end
53
+ end