sifiapi 0.9.0

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 (76) hide show
  1. data/.gitignore +18 -0
  2. data/Gemfile +8 -0
  3. data/LICENSE +22 -0
  4. data/README.md +83 -0
  5. data/Rakefile +7 -0
  6. data/examples/complete_workflow.rb +93 -0
  7. data/lib/faraday/request/convert_file_to_upload_io.rb +37 -0
  8. data/lib/faraday/request/json_encode.rb +11 -0
  9. data/lib/faraday/response/parse_json.rb +32 -0
  10. data/lib/faraday/response/raise_sifi_error.rb +37 -0
  11. data/lib/sifi_api/ad.rb +2 -0
  12. data/lib/sifi_api/ad_file_type.rb +2 -0
  13. data/lib/sifi_api/ad_size.rb +2 -0
  14. data/lib/sifi_api/bid_type.rb +2 -0
  15. data/lib/sifi_api/branded_datum.rb +2 -0
  16. data/lib/sifi_api/browser.rb +2 -0
  17. data/lib/sifi_api/campaign.rb +2 -0
  18. data/lib/sifi_api/campaign_stat.rb +2 -0
  19. data/lib/sifi_api/campaign_type.rb +2 -0
  20. data/lib/sifi_api/change.rb +2 -0
  21. data/lib/sifi_api/client.rb +2 -0
  22. data/lib/sifi_api/company.rb +2 -0
  23. data/lib/sifi_api/connection.rb +59 -0
  24. data/lib/sifi_api/context.rb +2 -0
  25. data/lib/sifi_api/dayparting.rb +2 -0
  26. data/lib/sifi_api/device.rb +2 -0
  27. data/lib/sifi_api/dma.rb +2 -0
  28. data/lib/sifi_api/domain.rb +2 -0
  29. data/lib/sifi_api/error.rb +15 -0
  30. data/lib/sifi_api/geo_target.rb +2 -0
  31. data/lib/sifi_api/ip_range.rb +2 -0
  32. data/lib/sifi_api/keyword.rb +2 -0
  33. data/lib/sifi_api/keyword_category.rb +2 -0
  34. data/lib/sifi_api/operating_system.rb +2 -0
  35. data/lib/sifi_api/recency.rb +2 -0
  36. data/lib/sifi_api/recurring_report.rb +2 -0
  37. data/lib/sifi_api/report.rb +2 -0
  38. data/lib/sifi_api/report_asset.rb +2 -0
  39. data/lib/sifi_api/report_type.rb +2 -0
  40. data/lib/sifi_api/resource.rb +150 -0
  41. data/lib/sifi_api/user.rb +2 -0
  42. data/lib/sifi_api/version.rb +9 -0
  43. data/lib/sifi_api.rb +41 -0
  44. data/lib/sifiapi.rb +1 -0
  45. data/sifiapi.gemspec +22 -0
  46. data/spec/sifi_api/ad_file_type_spec.rb +7 -0
  47. data/spec/sifi_api/ad_size_spec.rb +7 -0
  48. data/spec/sifi_api/ad_spec.rb +7 -0
  49. data/spec/sifi_api/bid_type_spec.rb +7 -0
  50. data/spec/sifi_api/branded_datum_spec.rb +7 -0
  51. data/spec/sifi_api/browser_spec.rb +7 -0
  52. data/spec/sifi_api/campaign_spec.rb +7 -0
  53. data/spec/sifi_api/campaign_stat_spec.rb +7 -0
  54. data/spec/sifi_api/campaign_type_spec.rb +7 -0
  55. data/spec/sifi_api/change_spec.rb +7 -0
  56. data/spec/sifi_api/client_spec.rb +7 -0
  57. data/spec/sifi_api/company_spec.rb +7 -0
  58. data/spec/sifi_api/context_spec.rb +7 -0
  59. data/spec/sifi_api/dayparting_spec.rb +7 -0
  60. data/spec/sifi_api/device_spec.rb +7 -0
  61. data/spec/sifi_api/dma_spec.rb +7 -0
  62. data/spec/sifi_api/domain_spec.rb +7 -0
  63. data/spec/sifi_api/geo_target_spec.rb +7 -0
  64. data/spec/sifi_api/ip_range_spec.rb +8 -0
  65. data/spec/sifi_api/keyword_category_spec.rb +7 -0
  66. data/spec/sifi_api/keyword_spec.rb +8 -0
  67. data/spec/sifi_api/operating_system_spec.rb +8 -0
  68. data/spec/sifi_api/recency_spec.rb +7 -0
  69. data/spec/sifi_api/recurring_report_spec.rb +8 -0
  70. data/spec/sifi_api/report_asset_spec.rb +8 -0
  71. data/spec/sifi_api/report_spec.rb +8 -0
  72. data/spec/sifi_api/report_type_spec.rb +8 -0
  73. data/spec/sifi_api/user_spec.rb +7 -0
  74. data/spec/spec_helper.rb +27 -0
  75. data/spec/support/resource_shared_example.rb +97 -0
  76. metadata +200 -0
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .DS_Store
4
+ .bundle
5
+ .config
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :development, :test do
6
+ gem "rake"
7
+ gem "rspec", "~> 2.7.0"
8
+ end
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Brandon Aaron
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.
data/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # SifiApi
2
+
3
+ Offers a simple way to interact with the Simpli.fi API via Ruby.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'sifiapi'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install sifiapi
18
+
19
+ ## Basic Usage
20
+
21
+ APPKEY = "your_app_key"
22
+ connection = SifiApi::Connection.new(APPKEY)
23
+
24
+ user = connection.user(user_key, :include => "companies,clients")
25
+
26
+ # see available resources
27
+ puts user.resources
28
+
29
+ # get the company and it's client
30
+ company = user.companies.first
31
+ client = company.clients.first
32
+
33
+ ## Create, Update, and Destroy Resource
34
+
35
+ campaign = client.create(:campaign)
36
+ campaign.update({ :name => "Testing via API!" })
37
+ campaign.delete
38
+
39
+ ## Capturing Errors
40
+
41
+ # Capture for all resources
42
+ class SifiApi::Resource
43
+ rescue_from SifiApi::NotFound, :with => proc{|e| puts "Not found!" }
44
+ rescue_from SifiApi::UnprocessableEntity, :with => proc{|e| puts "Failed to update!" }
45
+ end
46
+
47
+ # Capture per an individual resource
48
+ class SifiApi::User
49
+ rescue_from SifiApi::NotFound, :with => proc{|e| puts "User: Not found!" }
50
+ end
51
+
52
+ # Locally capture an error
53
+ begin
54
+ campaign.update({ :bid => "invalid" })
55
+ rescue SifiApi::UnprocessableEntity => exception
56
+ puts exception.message
57
+ end
58
+
59
+ ## Attaching Files to updates
60
+
61
+ keywords = campaign.keywords
62
+ csv = Tempfile.new(['keywords', 'csv'])
63
+ csv.print("keyword one,2.50\nkeyword two,2.25\nkeyword three,5.00")
64
+ csv.rewind
65
+ keywords.update(:csv => csv)
66
+
67
+ ## Downloading Files
68
+
69
+ keywords = campaign.keywords
70
+ csv = keywords.download
71
+ csv.is_a?(Tempfile) # true
72
+
73
+ ## Ruby Versions
74
+
75
+ This has been tested against Ruby 1.9.2.
76
+
77
+ ## Contributing
78
+
79
+ 1. Fork it
80
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
81
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
82
+ 4. Push to the branch (`git push origin my-new-feature`)
83
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require "rspec/core/rake_task"
4
+ RSpec::Core::RakeTask.new("spec") do |t|
5
+ t.rspec_opts = ["-c", "-f progress", "-r ./spec/spec_helper.rb"]
6
+ t.pattern = 'spec/**/*_spec.rb'
7
+ end
@@ -0,0 +1,93 @@
1
+ $:.unshift File.expand_path('..', __FILE__)
2
+ $:.unshift File.expand_path('../../lib', __FILE__)
3
+
4
+ require 'sifiapi'
5
+ require 'date'
6
+ require 'tempfile'
7
+
8
+ # add application and user api keys
9
+ APPKEY = 'APPLICATION_KEY_GOES_HERE'
10
+ USERKEY = 'USER_KEY_GOES_HERE'
11
+
12
+ api = SifiApi::Connection.new(APPKEY)
13
+ # can optionally pass a uri as the second param for the api end point (if you are testing against app-playground for instance)
14
+ #api = SifiApi::Connection.new(APPKEY, "https://app-playground.simpli.fi/api/")
15
+
16
+ # get the user and include the companies and clients in the result
17
+ user = api.user(USERKEY, :include => "companies,clients")
18
+
19
+ # grab the first company and the first companies client
20
+ company = user.companies.first
21
+ client = company.clients.first
22
+
23
+
24
+ # create a campaign from the client
25
+ search_campaign = client.create(:campaign)
26
+ search_campaign_settings = {
27
+ :name => "Example Search Campaign via API",
28
+ :campaign_type_id => 1,
29
+ :recency_id => 1,
30
+ :advertiser => "Example Advertiser",
31
+ :start_date => Date.today.strftime("%Y-%m-%d"),
32
+ :end_date => (Date.today + 30).strftime("%Y-%m-%d"),
33
+ :bid_type_id => 1,
34
+ :bid => 5.00,
35
+ :daily_budget => 500.00,
36
+ :dayparting_ids => [1,2],
37
+ :browser_ids => [601,604],
38
+ :device_ids => [701],
39
+ :operating_system_ids => [801],
40
+ :context_ids => [201,207],
41
+ :geo_target_ids => [1,8180],
42
+ :branded_data_ids => ['ECohortsDigital_26','ECohortsDigital_27'],
43
+ :frequency_capping => { :how_many_times => 1, :hours => 1 },
44
+ :campaign_goal => { :goal_type => "ctr", :goal_value => 0.05 },
45
+ :impression_cap => 10000,
46
+ :pacing => 75
47
+ }
48
+ # update the campaign with the desired settings
49
+ search_campaign.update(search_campaign_settings)
50
+
51
+ # reload the campaign and include the nested resources
52
+ puts search_campaign.reload(:include => "daypartings,browsers,devices,operating_systems,context_ids,branded_data").inspect
53
+
54
+
55
+ # setup an ad
56
+ html_ad_settings = {
57
+ :name => "Example HTML Ad",
58
+ :ad_file_type_id => 4,
59
+ :ad_size_id => 3,
60
+ :target_url => "http://www.simpli.fi/",
61
+ :html => "<a href=\"{{clickTag}}\" target=\"_blank\">Simpli.fi</a>"
62
+ }
63
+ search_campaign.create(:ad, html_ad_settings)
64
+
65
+ puts search_campaign.ads.inspect
66
+
67
+ # upload some keywords for the campaign
68
+ keywords = search_campaign.keywords.first
69
+ keyword_csv = Tempfile.new(['keywords', 'csv'])
70
+ keyword_csv.print("keyword one,2.50\nkeyword two,2.25\nkeyword three,5.00")
71
+ keyword_csv.rewind
72
+ keywords.update(:csv => keyword_csv)
73
+
74
+ puts keywords.reload.inspect
75
+
76
+ # upload some domains
77
+ domains = search_campaign.domains.first
78
+ domain_csv = Tempfile.new(['domains', 'csv'])
79
+ domain_csv.print("http://simpli.fi/")
80
+ domain_csv.rewind
81
+ domains.update(:csv => domain_csv, :list_type => "whitelist")
82
+
83
+ puts domains.reload.inspect
84
+
85
+ # upload some ip ranges
86
+ ip_ranges = search_campaign.ip_ranges.first
87
+ ip_ranges_csv = Tempfile.new(['ip_ranges', 'csv'])
88
+ ip_ranges_csv.print("192.168.1.1,192.168.1.9\n192.168.2.1,192.168.2.9")
89
+ ip_ranges_csv.rewind
90
+ ip_ranges.update(:csv => ip_ranges_csv)
91
+
92
+ puts ip_ranges.reload.inspect
93
+
@@ -0,0 +1,37 @@
1
+ module Faraday
2
+ class Request::ConvertFileToUploadIO < Faraday::Middleware
3
+ def call(env)
4
+ if env[:body].is_a?(Hash)
5
+ resource = env[:body].keys.first
6
+ env[:body][resource].each do |key, value|
7
+ if value.is_a?(File) || value.is_a?(Tempfile)
8
+ env[:body][resource][key] = Faraday::UploadIO.new(value, mime_type(value.path), value.path)
9
+ elsif value.is_a?(Hash) && (value['io'].is_a?(IO) || value['io'].is_a?(StringIO))
10
+ env[:body][resource][key] = Faraday::UploadIO.new(value['io'], mime_type('.'+value['type']), '')
11
+ end
12
+ end
13
+ end
14
+ @app.call(env)
15
+ end
16
+
17
+ private
18
+
19
+ def mime_type(path)
20
+ case path
21
+ when /\.jpe?g/i
22
+ 'image/jpeg'
23
+ when /\.gif$/i
24
+ 'image/gif'
25
+ when /\.png$/i
26
+ 'image/png'
27
+ when /\.swf$/i
28
+ 'application/swf'
29
+ when /\.csv$/i
30
+ 'text/csv'
31
+ else
32
+ 'application/octet-stream'
33
+ end
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,11 @@
1
+ module Faraday
2
+ class Request::JsonEncode < Faraday::Middleware
3
+ def call(env)
4
+ if env[:request_headers]["Content-Type"] == nil
5
+ env[:body] = MultiJson.encode(env[:body]) if env[:body] != nil
6
+ env[:request_headers]["Content-Type"] = "application/json"
7
+ end
8
+ @app.call(env)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,32 @@
1
+ module Faraday
2
+ class Response::ParseJson < Faraday::Middleware
3
+ dependency 'multi_json'
4
+
5
+ def call(env)
6
+ @app.call(env).on_complete do
7
+ if env[:response_headers]["content-type"].include?("application/json")
8
+ env[:body] = convert_to_json(env[:body])
9
+ end
10
+ end
11
+ end
12
+
13
+ protected
14
+
15
+ def convert_to_json(body)
16
+ case body.strip
17
+ when ''
18
+ nil
19
+ when 'true'
20
+ true
21
+ when 'false'
22
+ false
23
+ else
24
+ begin
25
+ ::MultiJson.decode(body)
26
+ rescue Exception => exception
27
+ raise SifiApi::InvalidJson
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,37 @@
1
+ module Faraday
2
+ class Response::RaiseSifiError < Response::Middleware
3
+ def on_complete(response)
4
+ case response[:status].to_i
5
+ when 400
6
+ raise SifiApi::BadRequest, error_message(response)
7
+ when 401
8
+ raise SifiApi::Unauthorized, error_message(response)
9
+ when 403
10
+ raise SifiApi::Forbidden, error_message(response)
11
+ when 404
12
+ raise SifiApi::NotFound, error_message(response)
13
+ when 406
14
+ raise SifiApi::NotAcceptable, error_message(response)
15
+ when 422
16
+ raise SifiApi::UnprocessableEntity, error_message(response)
17
+ when 500
18
+ raise SifiApi::InternalServerError, error_message(response)
19
+ when 501
20
+ raise SifiApi::NotImplemented, error_message(response)
21
+ when 502
22
+ raise SifiApi::BadGateway, error_message(response)
23
+ when 503
24
+ raise SifiApi::ServiceUnavailable, error_message(response)
25
+ end
26
+ end
27
+
28
+ def error_message(response)
29
+ msg = "#{response[:method].to_s.upcase} #{response[:url].to_s}: #{response[:status]}"
30
+ if errors = response[:body] && response[:body]["errors"]
31
+ msg << "\n"
32
+ msg << errors.join("\n")
33
+ end
34
+ msg
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,2 @@
1
+ class SifiApi::Ad < SifiApi::Resource
2
+ end
@@ -0,0 +1,2 @@
1
+ class SifiApi::AdFileType < SifiApi::Resource
2
+ end
@@ -0,0 +1,2 @@
1
+ class SifiApi::AdSize < SifiApi::Resource
2
+ end
@@ -0,0 +1,2 @@
1
+ class SifiApi::BidType < SifiApi::Resource
2
+ end
@@ -0,0 +1,2 @@
1
+ class SifiApi::BrandedDatum < SifiApi::Resource
2
+ end
@@ -0,0 +1,2 @@
1
+ class SifiApi::Browser < SifiApi::Resource
2
+ end
@@ -0,0 +1,2 @@
1
+ class SifiApi::Campaign < SifiApi::Resource
2
+ end
@@ -0,0 +1,2 @@
1
+ class SifiApi::CampaignStat < SifiApi::Resource
2
+ end
@@ -0,0 +1,2 @@
1
+ class SifiApi::CampaignType < SifiApi::Resource
2
+ end
@@ -0,0 +1,2 @@
1
+ class SifiApi::Change < SifiApi::Resource
2
+ end
@@ -0,0 +1,2 @@
1
+ class SifiApi::Client < SifiApi::Resource
2
+ end
@@ -0,0 +1,2 @@
1
+ class SifiApi::Company < SifiApi::Resource
2
+ end
@@ -0,0 +1,59 @@
1
+ require 'faraday/request/convert_file_to_upload_io'
2
+ require 'faraday/request/json_encode'
3
+ require 'faraday/response/raise_sifi_error'
4
+ require 'faraday/response/parse_json'
5
+
6
+ class SifiApi::Connection
7
+ def initialize(app_key, site="https://app.simpli.fi/api/")
8
+ @site = site
9
+ @app_key = app_key
10
+ @connection = Faraday::Connection.new(:url => site, :ssl => { :verify => false }) do |builder|
11
+ builder.use Faraday::Request::ConvertFileToUploadIO
12
+ builder.request :multipart
13
+ builder.use Faraday::Request::JsonEncode
14
+
15
+ builder.use Faraday::Response::RaiseSifiError
16
+ builder.use Faraday::Response::ParseJson
17
+
18
+ builder.adapter Faraday.default_adapter
19
+ end
20
+ end
21
+
22
+ def user(user_key, params={})
23
+ SifiApi::User.get_via_uri(self, user_key, '', params).first
24
+ end
25
+
26
+ def get(user_key, route='', params={}, headers={})
27
+ begin
28
+ @connection.run_request(:get, @connection.build_url(route, params), nil, { "X-App-Key" => @app_key, "X-User-Key" => user_key }.merge(headers))
29
+ rescue Faraday::Error::TimeoutError => e
30
+ raise SifiApi::Timeout, "Timeout"
31
+ end
32
+ end
33
+
34
+ def post(user_key, route='', body=nil, headers={})
35
+ begin
36
+ @connection.run_request(:post, route, body, { "X-App-Key" => @app_key, "X-User-Key" => user_key }.merge(headers))
37
+ rescue Faraday::Error::TimeoutError => e
38
+ raise SifiApi::Timeout, "Timeout"
39
+ end
40
+ end
41
+
42
+ def put(user_key, route='', body=nil, headers={})
43
+ begin
44
+ @connection.run_request(:put, route, body, { "X-App-Key" => @app_key, "X-User-Key" => user_key }.merge(headers))
45
+ rescue Faraday::Error::TimeoutError => e
46
+ raise SifiApi::Timeout, "Timeout"
47
+ end
48
+ end
49
+
50
+ def delete(user_key, route='', body=nil, headers={})
51
+ begin
52
+ response = @connection.run_request(:delete, route, body, { "X-App-Key" => @app_key, "X-User-Key" => user_key }.merge(headers))
53
+ response[:status] == 204
54
+ rescue Faraday::Error::TimeoutError => e
55
+ raise SifiApi::Timeout, "Timeout"
56
+ end
57
+ end
58
+
59
+ end
@@ -0,0 +1,2 @@
1
+ class SifiApi::Context < SifiApi::Resource
2
+ end
@@ -0,0 +1,2 @@
1
+ class SifiApi::Dayparting < SifiApi::Resource
2
+ end
@@ -0,0 +1,2 @@
1
+ class SifiApi::Device < SifiApi::Resource
2
+ end
@@ -0,0 +1,2 @@
1
+ class SifiApi::Dma < SifiApi::Resource
2
+ end
@@ -0,0 +1,2 @@
1
+ class SifiApi::Domain < SifiApi::Resource
2
+ end
@@ -0,0 +1,15 @@
1
+ module SifiApi
2
+ class Error < StandardError; end
3
+ class BadRequest < Error; end
4
+ class Unauthorized < Error; end
5
+ class Forbidden < Error; end
6
+ class NotFound < Error; end
7
+ class NotAcceptable < Error; end
8
+ class UnprocessableEntity < Error; end
9
+ class InternalServerError < Error; end
10
+ class NotImplemented < Error; end
11
+ class BadGateway < Error; end
12
+ class ServiceUnavailable < Error; end
13
+ class InvalidJson < Error; end
14
+ class Timeout < Error; end
15
+ end
@@ -0,0 +1,2 @@
1
+ class SifiApi::GeoTarget < SifiApi::Resource
2
+ end
@@ -0,0 +1,2 @@
1
+ class SifiApi::IpRange < SifiApi::Resource
2
+ end
@@ -0,0 +1,2 @@
1
+ class SifiApi::Keyword < SifiApi::Resource
2
+ end
@@ -0,0 +1,2 @@
1
+ class SifiApi::KeywordCategory < SifiApi::Resource
2
+ end
@@ -0,0 +1,2 @@
1
+ class SifiApi::OperatingSystem < SifiApi::Resource
2
+ end
@@ -0,0 +1,2 @@
1
+ class SifiApi::Recency < SifiApi::Resource
2
+ end
@@ -0,0 +1,2 @@
1
+ class SifiApi::RecurringReport < SifiApi::Resource
2
+ end
@@ -0,0 +1,2 @@
1
+ class SifiApi::Report < SifiApi::Resource
2
+ end
@@ -0,0 +1,2 @@
1
+ class SifiApi::ReportAsset < SifiApi::Resource
2
+ end
@@ -0,0 +1,2 @@
1
+ class SifiApi::ReportType < SifiApi::Resource
2
+ end