projector 0.1.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c2b3dbd1610c6ce053ccd4e5a0566b6f77475ff5
4
+ data.tar.gz: d39d7bcb4f57f8e9970de3b461db9b71f4ae4a75
5
+ SHA512:
6
+ metadata.gz: a716767bae171e383ccc6ce0081bd7dcc520efabf3a6134e5556d1e08e87ba7dc14131b1347f71609b229ac115bdc170c83d126fa31d2b1f77a8d420144c42c5
7
+ data.tar.gz: ab4278cfe220f32606c78a457414a57af849e5bf6dd4f10512e48668b194a28cfdf0ee5aea05cb76aa2d738490233c0c7464c4ff7cfd96ce57394d4bf1f31a93
data/.gitignore CHANGED
@@ -1,4 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
1
10
  *.gem
2
- .bundle
3
- Gemfile.lock
4
- pkg/*
11
+ .ruby-version
data/Gemfile CHANGED
@@ -1,4 +1,18 @@
1
- source "http://rubygems.org"
1
+ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in projector.gemspec
4
4
  gemspec
5
+
6
+ gem 'rake', :group => [:development, :test]
7
+
8
+ # Testing dependencies
9
+ group :test do
10
+ gem 'minitest'
11
+ gem 'webmock', :require => 'webmock/minitest'
12
+ gem 'mocha', :require => 'mocha/setup'
13
+ gem 'simplecov', :require => false
14
+ gem 'm'
15
+ gem 'addressable'
16
+ gem 'factory_girl'
17
+ gem 'timecop'
18
+ end
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright (c) 2016, Orphid, Inc.
2
+
3
+ All rights reserved.
4
+
5
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
6
+
7
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
8
+
9
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
10
+
11
+ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
12
+
13
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md CHANGED
@@ -1,44 +1,21 @@
1
- # Projector
1
+ # Projector Ruby SDK
2
2
 
3
- A simple command-line app to keep your projects directory up to date with all of your Github projects. I got tired of having to re-clone new projects all the time, and figured there had to be an easier way. Turns out there wasn't, so I built one.
3
+ [SDK Guide](https://goprojector.readme.io/docs/ruby-1)
4
4
 
5
- ## Dependencies
5
+ ## Installation
6
6
 
7
- Requires the [Thor](https://github.com/wycats/thor) and JSON gems. You'll also need a Github account and API key.
7
+ The `projector` gem is hosted on Gemfury so you will need to add the Gemfury repository to your gemfile:
8
8
 
9
- ## Installation and Usage
9
+ ```
10
+ gem 'projector', :source => 'https://gem.fury.io/projector/'
11
+ ```
10
12
 
11
- Install the gem:
12
13
 
13
- gem install projector
14
+ ## Building / Testing
14
15
 
15
- Configure your Github settings (if you haven't done so already). Details are [here](http://help.github.com/set-your-user-name-email-and-github-token/), but the short version is:
16
+ ### Testing
16
17
 
17
- git config --global github.user <username>
18
- git config --global github.token <token>
19
-
20
- Configure your working directory. I have a `Projects` directory under my home directory where I keep all of my working copies. Adjust to your own convention as needed.
21
-
22
- git config --global projector.workingdir ~/Projects
23
-
24
- Run `projector checkout`. Projector will find all of the repos you have access to and prompt you to clone them under your working directory if they're not already cloned. By default, it will create a nested directory structure based on the repository owner, something like this:
25
-
26
- jayzes/
27
- jayzes/projector
28
- jayzes/cucumber-api-steps
29
- gvarela/
30
- gvarela/food_court_recipes
31
-
32
- If you want it to forge ahead and clone everything, there's a `-a` option that assumes yes to every clone confirmation and doesn't bother prompting.
33
-
34
- ## Future Ideas
35
- * Skiplist/repo ignore regexes
36
- * Checking for repos that aren't under version control and prompting to create or prune them
37
- * An easier way to run this under cron
38
-
39
- # Author
40
- Jay Zeschin
41
-
42
- ## Copyright
43
-
44
- Copyright (c) 2011 Jay Zeschin. Distributed under the MIT License.
18
+ ```
19
+ bin/setup
20
+ bundle exec run test
21
+ ```
data/Rakefile CHANGED
@@ -1,2 +1,30 @@
1
1
  require 'bundler'
2
2
  Bundler::GemHelper.install_tasks
3
+
4
+ require 'rake/testtask'
5
+ Rake::TestTask.new(:test) do |t|
6
+ t.libs << 'test'
7
+ t.pattern = 'test/**/*_test.rb'
8
+ end
9
+
10
+ task :console do
11
+ require 'irb'
12
+ require 'irb/completion'
13
+ require 'projector'
14
+ ARGV.clear
15
+ IRB.start
16
+ end
17
+
18
+ task :default => :test
19
+
20
+ begin
21
+ require 'yard'
22
+ YARD::Rake::YardocTask.new(:doc) do |task|
23
+ task.files = ['Readme.markdown', 'LICENSE', 'lib/**/*.rb']
24
+ task.options = [
25
+ '--output-dir', 'doc',
26
+ '--markup', 'markdown',
27
+ ]
28
+ end
29
+ rescue LoadError
30
+ end
data/bin/console ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "projector"
5
+ require "irb"
6
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
data/circle.yml ADDED
@@ -0,0 +1,3 @@
1
+ machine:
2
+ ruby:
3
+ version: 2.2.3
@@ -0,0 +1,30 @@
1
+ module Projector
2
+ module ApiMethods
3
+ def register_end_user(end_user)
4
+ # If a hash is passed, use it to populate a user object
5
+ if end_user.instance_of?(Hash)
6
+ user = Projector::EndUser.new(end_user)
7
+ # If a User instance is passed, use it
8
+ elsif end_user.instance_of?(Projector::EndUser)
9
+ user = end_user
10
+ end
11
+ # return the data element of the resulting envelope
12
+ post(host, '/end_users', Projector::Envelope.new(Projector::EndUser::SCHEMA, user).to_hash).body
13
+ end
14
+
15
+ def set_end_user_tags(id, tags)
16
+ user = Projector::EndUser.new(id: id, tags: tags)
17
+ register_end_user(user)
18
+ end
19
+
20
+ def deliver_event(event)
21
+ # Create an Event instance if the parameter is a Hash.
22
+ if event.instance_of?(Hash)
23
+ event = Projector::Event.new(event)
24
+ end
25
+ # Verify that we've got an Event object.
26
+ raise Projector::InvalidEvent.new("Invalid event data") unless event.instance_of?(Projector::Event)
27
+ post(host, '/events', Projector::Envelope.new(Projector::Event::SCHEMA, event).to_hash).body
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,46 @@
1
+ module Projector
2
+ require 'projector/transport/http'
3
+ require 'projector/error'
4
+ require 'projector/envelope'
5
+ require 'projector/end_user'
6
+ require 'projector/event'
7
+ require 'projector/api_methods'
8
+
9
+ class Client
10
+ include Projector::ApiMethods
11
+ include Projector::Transport::HTTP
12
+
13
+ attr_accessor :host, :token, :result_format
14
+
15
+ # The current configuration parameters for all clients
16
+ def self.options
17
+ @options ||= {
18
+ host: ENV['PROJECTOR_API_HOST'] || 'https://api.projector.com'
19
+ }
20
+ end
21
+
22
+ # Sets the current configuration parameters for all clients
23
+ def self.options=(val)
24
+ @options = val
25
+ end
26
+
27
+ # Allows the setting of configuration parameters in a configure block.
28
+ def self.configure
29
+ yield options
30
+ end
31
+
32
+ # Initialize a new client.
33
+ #
34
+ # @param options [Hash] optionally specify `:access_token`, `:host`, `:api_version`
35
+ def initialize(options = {})
36
+ options = self.class.options.merge(options)
37
+ @host = options[:host] || ENV['PROJECTOR_API_HOST']
38
+ @token = options[:token] || ENV['PROJECTOR_API_TOKEN']
39
+ @options = options
40
+ end
41
+
42
+ def host
43
+ @options[:host]
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,27 @@
1
+ module Projector
2
+ class EndUser
3
+
4
+ SCHEMA = 'http://schemas.projector.com/schemas/com.projector/server_api/jsonschema/0-9-1#/definitions/end_user_post'
5
+
6
+ attr_accessor :id, :tags
7
+
8
+ def initialize(params = {})
9
+ raise Projector::InvalidEndUser.new("Invalid end user, id missing") if params[:id].nil?
10
+ if params.instance_of?(Hash)
11
+ params.each do |k, v|
12
+ self.send("#{k}=", v) if self.respond_to?("#{k}=")
13
+ end
14
+ else
15
+ self.id = params.to_s
16
+ end
17
+ end
18
+
19
+ def to_hash
20
+ {
21
+ customer_end_user_id: id.to_s,
22
+ tags: (tags || []),
23
+ profile: {}
24
+ }
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,22 @@
1
+ module Projector
2
+ class Envelope
3
+ attr_accessor :schema, :data
4
+
5
+ def initialize(schema, data)
6
+ @schema = schema
7
+ @data = data
8
+ end
9
+
10
+ def payload
11
+ data.respond_to?(:to_hash) ? data.to_hash : data
12
+ end
13
+
14
+ def to_hash(params = {})
15
+ raise Projector::InvalidEnvelope unless @data && @schema
16
+ {
17
+ schema: schema,
18
+ data: payload
19
+ }
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,61 @@
1
+ module Projector
2
+ # Standard Projector error
3
+ class Error < StandardError
4
+ attr_accessor :request, :response, :response_body, :request_id
5
+ end
6
+
7
+ # Raised when Projector returns a 400 HTTP status code
8
+ class BadRequest < Error; end
9
+
10
+ # Raised when Projector returns a 401 HTTP status code
11
+ class Unauthorized < Error; end
12
+
13
+ # Raised when Projector returns a 403 HTTP status code
14
+ class Forbidden < Error; end
15
+
16
+ # Raised when Projector returns a 404 HTTP status code
17
+ class NotFound < Error; end
18
+
19
+ # Raised when Projector returns a 406 HTTP status code
20
+ class NotAcceptable < Error; end
21
+
22
+ # Raised when Projector returns a 422 HTTP status code
23
+ class UnprocessableEntity < Error; end
24
+
25
+ # Raised when Projector returns a 500 HTTP status code
26
+ class InternalServerError < Error;
27
+ attr_accessor :retry_after
28
+ end
29
+
30
+ # Raised when Projector returns a 501 HTTP status code
31
+ class NotImplemented < Error; end
32
+
33
+ # Raised when Projector returns a 502 HTTP status code
34
+ class BadGateway < Error; end
35
+
36
+ # Raised when Projector returns a 503 HTTP status code
37
+ class ServiceUnavailable < Error; end
38
+
39
+ # Raised when a unique ID is required but not provided
40
+ class UniqueIDRequired < Error; end
41
+
42
+ class InvalidEndUser < StandardError; end
43
+ class InvalidEvent < StandardError; end
44
+ class InvalidDevice < StandardError; end
45
+ class InvalidRegistration < StandardError; end
46
+ class InvalidEnvelope < StandardError; end
47
+
48
+ # Status code to exception map
49
+ ERROR_MAP = {
50
+ 400 => Projector::BadRequest,
51
+ 401 => Projector::Unauthorized,
52
+ 403 => Projector::Forbidden,
53
+ 404 => Projector::NotFound,
54
+ 406 => Projector::NotAcceptable,
55
+ 422 => Projector::UnprocessableEntity,
56
+ 500 => Projector::InternalServerError,
57
+ 501 => Projector::InternalServerError,
58
+ 502 => Projector::InternalServerError,
59
+ 503 => Projector::InternalServerError
60
+ }
61
+ end
@@ -0,0 +1,19 @@
1
+ module Projector
2
+ require 'securerandom'
3
+
4
+ class Event
5
+ SCHEMA = 'http://schemas.projector.com/schemas/com.projector/events_api/jsonschema/0-9-3#/links/0/schema'
6
+
7
+ def initialize(params)
8
+ @params = params.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
9
+ end
10
+
11
+ def to_hash
12
+ overrides = {
13
+ id: @params[:id] ? @params[:id].to_s : SecureRandom.uuid,
14
+ event_time: ((@params[:event_time].instance_of?(Time) ? @params[:event_time] : Time.now).to_f * 1000).to_i
15
+ }
16
+ @params.merge overrides
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,203 @@
1
+ module Projector
2
+ module Transport
3
+ # Persistent HTTP API transport adapter
4
+ module HTTP
5
+ require 'net/http/persistent'
6
+ require 'uri'
7
+ require 'json'
8
+
9
+ MAX_REDIRECTS = 2
10
+
11
+ [:get, :post, :put, :delete, :patch].each do |method|
12
+ define_method method do |*args|
13
+ json_request(method, *args)
14
+ end
15
+ end
16
+
17
+ protected
18
+
19
+ def request(method, host, path, params = nil, redirect_count = 0)
20
+ uri = URI.parse("#{host}#{path}")
21
+
22
+ # if the request requires parameters in the query string, merge them in
23
+ if params && !can_post_data?(method)
24
+ query_values = uri.query ? URI.decode_www_form(uri.query).inject({}) {|h,(k,v)| h[k]=v; h} : {}
25
+ uri.query = to_url_params((query_values || {}).merge(params))
26
+ end
27
+
28
+ # Build request
29
+ request = build_request(method, uri)
30
+
31
+ # Add headers
32
+ request['Authorization'] = "Token #{self.token}"
33
+ request['Content-Type'] = 'application/json'
34
+ request['Accept'] = "application/vnd.projector+json;"
35
+ request['Request-Id'] = SecureRandom.uuid
36
+
37
+ # Add params as JSON if they exist
38
+ request.body = JSON.generate(params) if can_post_data?(method) && params
39
+
40
+ log_debug_data(uri, request.method, request.body)
41
+
42
+ # Request
43
+ response = http.request(uri, request)
44
+
45
+ # Log debug data if needed
46
+ log_debug_data(uri, response.code, response.body)
47
+
48
+ # If we've been given a redirect
49
+ if [301, 302].include?(response.code.to_i)
50
+
51
+ # Make sure we aren't in a giant redirect loop
52
+ if redirect_count >= MAX_REDIRECTS
53
+ err = TooManyRedirectsError.new
54
+ err.request = request
55
+ err.response = response
56
+ err.redirect_count = redirect_count
57
+ raise err
58
+ end
59
+
60
+ # Parse the new location
61
+ uri = URI.parse(response['Location'])
62
+ # Make the call to the redirected location, incrementing the redirect count by 1
63
+ return request(method, uri.select(:scheme, :host).join("://"), uri.path, params, redirect_count + 1)
64
+ # Return the result of the redirected request
65
+ end
66
+
67
+ # Check for errors
68
+ handle_error(request, response)
69
+
70
+
71
+ # Return the raw response object
72
+ response
73
+ end
74
+
75
+ def http
76
+ @http ||= begin
77
+ http = Net::HTTP::Persistent.new('projector')
78
+ http.open_timeout = 5
79
+ http.read_timeout = 2
80
+ http
81
+ end
82
+ end
83
+
84
+ def build_request(method, uri)
85
+ case method
86
+ when :get
87
+ Net::HTTP::Get.new(uri.request_uri)
88
+ when :post
89
+ Net::HTTP::Post.new(uri.request_uri)
90
+ when :put
91
+ Net::HTTP::Put.new(uri.request_uri)
92
+ when :delete
93
+ Net::HTTP::Delete.new(uri.request_uri)
94
+ when :patch
95
+ Net::HTTP::Patch.new(uri.request_uri)
96
+ end
97
+ end
98
+
99
+ def to_url_params(hash)
100
+ params = []
101
+ hash.each_pair do |key, value|
102
+ params << param_for(key, value).flatten
103
+ end
104
+ params.sort.join('&')
105
+ end
106
+
107
+ def param_for(key, value, parent = nil)
108
+ if value.is_a?(Hash)
109
+ params = []
110
+ value.each_pair do |value_key, value_value|
111
+ value_parent = parent ? parent + "[#{key}]" : key.to_s
112
+ params << param_for(value_key, value_value, value_parent)
113
+ end
114
+ params
115
+ else
116
+ ["#{parent ? parent + "[#{key}]" : key.to_s}=#{CGI::escape(value.to_s)}"]
117
+ end
118
+ end
119
+
120
+ def handle_error(request, response)
121
+ return unless error = Projector::ERROR_MAP[response.code.to_i]
122
+ message = nil
123
+ begin
124
+ message = JSON.parse(response.body) || request.path
125
+ rescue JSON::ParserError => e
126
+ end
127
+ # Raise error
128
+ err = error.new(message)
129
+ err.retry_after = response['retry-after'].to_i if response.code.to_i >= 500
130
+ err.request = request
131
+ err.response = response
132
+ err.response_body = message
133
+ err.request_id = request['request-id']
134
+ raise err
135
+ end
136
+
137
+ def json_request(*args)
138
+ # Perform request, pass result format
139
+ Response.new(request(*args))
140
+ end
141
+
142
+ def boolean_from_response(*args)
143
+ response = request(*args)
144
+ (200..299).include? response.code.to_i
145
+ end
146
+
147
+ def can_post_data?(method)
148
+ [:post, :put, :patch].include?(method)
149
+ end
150
+
151
+ def log_debug_data(*args)
152
+ return unless ENV['PROJECTOR_DEBUG_HTTP']
153
+ @logger ||= begin
154
+ require 'logger'
155
+ Logger.new(STDOUT)
156
+ end
157
+ @logger.info(args.reject{|s| s = s.to_s; s.nil? || s.empty?}.join(' - '))
158
+ end
159
+
160
+ # Response class responsible for deserializing API calls
161
+ # @attr [Object] body The parsed response
162
+ # @attr [Hash] headers HTTP headers returned as part of the response
163
+ # @attr [String] code The HTTP response code
164
+ class Response
165
+ attr_accessor :body, :headers, :code
166
+
167
+ # Initializes a new result
168
+ #
169
+ # @param http_response [Net::HTTPResponse] the raw response to parse
170
+ def initialize(http_response)
171
+ @code = http_response.code
172
+ parse_headers(http_response.to_hash)
173
+ parse_body(http_response.body)
174
+ end
175
+
176
+ private
177
+
178
+ # Parses raw headers in to a reasonable hash
179
+ def parse_headers(raw_headers)
180
+ # raw headers from net_http have an array for each result. Flatten them out.
181
+ @headers = raw_headers.inject({}) do |remainder, (k, v)|
182
+ remainder[k] = [v].flatten.first
183
+ remainder
184
+ end
185
+ end
186
+
187
+ # Parses the raw body in to a hash
188
+ def parse_body(response_body)
189
+ # Parse JSON
190
+ begin
191
+ self.body = JSON.parse(response_body)
192
+ rescue JSON::ParserError => ex
193
+ self.body = response_body
194
+ end
195
+ end
196
+ end
197
+
198
+ class TooManyRedirectsError < StandardError
199
+ attr_accessor :request, :response, :redirect_count
200
+ end
201
+ end
202
+ end
203
+ end
@@ -1,3 +1,3 @@
1
1
  module Projector
2
- VERSION = "0.1.0"
2
+ VERSION = "1.0.0"
3
3
  end
data/lib/projector.rb CHANGED
@@ -1,7 +1,57 @@
1
- require 'projector/repo'
2
- require 'projector/helpers'
3
- require 'projector/cli'
4
1
  require 'projector/version'
2
+ require 'projector/client'
5
3
 
6
4
  module Projector
7
- end
5
+ class << self
6
+ # Alias for Projector::Client.new
7
+ #
8
+ # @return [Projector::Client]
9
+ def new(options = {})
10
+ Projector::Client.new(options)
11
+ end
12
+
13
+ # Delegate to Projector::Client.new
14
+ def method_missing(method, *args, &block)
15
+ return super unless new.respond_to?(method)
16
+ new.send(method, *args, &block)
17
+ end
18
+
19
+ # Forward respond_to? to Projector::Client.new
20
+ def respond_to?(method, include_private = false)
21
+ new.respond_to?(method, include_private) || super(method, include_private)
22
+ end
23
+
24
+ def logger
25
+ @logger ||= begin
26
+ if defined?(::Rails)
27
+ Rails.logger
28
+ else
29
+ require 'logger'
30
+ ::Logger.new(STDOUT)
31
+ end
32
+ end
33
+ end
34
+
35
+ def logger=(x)
36
+ @logger = x
37
+ end
38
+
39
+ DEFAULT_ERROR_HANDLER = Proc.new do |ex, ctx|
40
+ Projector.logger.error(ex)
41
+ Projector.logger.error(ctx.inspect)
42
+ ex.backtrace.each do |line|
43
+ Projector.logger.error(line)
44
+ end if ex.backtrace
45
+ end
46
+
47
+ ##
48
+ # Add a global error handler for any async errors Projector encounters.
49
+ # Error handlers must respond to `call(exception, context_hash)`:
50
+ #
51
+ # Projector.error_handlers << Proc.new {|ex, ctx| do_something(ex) }
52
+ #
53
+ def error_handlers
54
+ @handlers ||= [DEFAULT_ERROR_HANDLER]
55
+ end
56
+ end
57
+ end
data/projector.gemspec CHANGED
@@ -1,22 +1,21 @@
1
- # -*- encoding: utf-8 -*-
2
- $:.push File.expand_path("../lib", __FILE__)
3
- require "projector/version"
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'projector/version'
4
5
 
5
- Gem::Specification.new do |s|
6
- s.name = "projector"
7
- s.version = Projector::VERSION
8
- s.platform = Gem::Platform::RUBY
9
- s.authors = ["Jay Zeschin"]
10
- s.email = ["jay@zeschin.org"]
11
- s.homepage = "https://github.com/jayzes/projector"
12
- s.summary = %q{Automatically keep a working directory with checkouts of all your Github projects}
13
- s.description = %q{Automatically keep a working directory with checkouts of all your Github projects}
14
-
15
- s.add_dependency 'json'
16
- s.add_dependency 'thor'
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "projector"
8
+ spec.version = Projector::VERSION
9
+ spec.authors = ["Projector"]
10
+ spec.email = ["engineering@projector.com"]
17
11
 
18
- s.files = `git ls-files`.split("\n")
19
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
20
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
21
- s.require_paths = ["lib"]
12
+ spec.summary = %q{Ruby SDK for interacting with the Projector SDK}
13
+ spec.description = %q{Create and manage users, devices, and events going to Projector.}
14
+ spec.homepage = "https://www.projector.com"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.require_paths = ["lib"]
18
+ spec.license = "BSD-3-Clause"
19
+
20
+ spec.add_dependency 'net-http-persistent', '~> 2.9', '>= 2.9.4'
22
21
  end
data/sample/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'sinatra'
4
+ gem 'sinatra-contrib'
5
+
6
+ gem 'projector', :source => 'https://gem.fury.io/projector/'
@@ -0,0 +1,37 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ remote: https://gem.fury.io/projector/
4
+ specs:
5
+ backports (3.6.8)
6
+ multi_json (1.11.2)
7
+ net-http-persistent (2.9.4)
8
+ projector (1.0.0)
9
+ net-http-persistent (~> 2.9, >= 2.9.4)
10
+ rack (1.6.4)
11
+ rack-protection (1.5.3)
12
+ rack
13
+ rack-test (0.6.3)
14
+ rack (>= 1.0)
15
+ sinatra (1.4.7)
16
+ rack (~> 1.5)
17
+ rack-protection (~> 1.4)
18
+ tilt (>= 1.3, < 3)
19
+ sinatra-contrib (1.4.6)
20
+ backports (>= 2.0)
21
+ multi_json
22
+ rack-protection
23
+ rack-test
24
+ sinatra (~> 1.4.0)
25
+ tilt (>= 1.3, < 3)
26
+ tilt (2.0.2)
27
+
28
+ PLATFORMS
29
+ ruby
30
+
31
+ DEPENDENCIES
32
+ projector!
33
+ sinatra
34
+ sinatra-contrib
35
+
36
+ BUNDLED WITH
37
+ 1.11.2
data/sample/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # Projector Ruby SDK - Sample App Implementation
2
+
3
+ This directory houses a sample Sinatra implementation app using the Projector Ruby SDK.
4
+
5
+ Be sure to grab your Projector token from the dashboard and update the `config.yml` with it.
6
+
7
+ You should be able to run the application and hit the endpoints in `index.rb` with the following commands.
8
+
9
+ ```sh
10
+ > (cd ../; gem build projector.gemspec)
11
+ > bundle install
12
+ > bundle exec rackup -p 8080
13
+ ```
14
+
15
+ The application is configured to automatically send test events on a scheduled timer every 10 s.
16
+
17
+ You should see logging in the session std out.
18
+
19
+ ## Sample curl
20
+
21
+ ```sh
22
+ # In order to test the registration flow:
23
+ curl -XPOST localhost:8080/register_projector -d ''
24
+
25
+ # In order to send a tag-targeted event
26
+ curl -XPOST localhost:8080/cheeseburger_alert -d ''
27
+
28
+ # In order to send an end user-targeted event
29
+ curl -XPOST localhost:8080/alert_jane -d ''
30
+ ```
data/sample/config.ru ADDED
@@ -0,0 +1,3 @@
1
+ require './index'
2
+
3
+ run Sample
data/sample/config.yml ADDED
@@ -0,0 +1,2 @@
1
+ host: https://api.projector.com
2
+ token: <CHANGE ME TO PROJECTOR TOKEN FROM DASHBOARD>
data/sample/index.rb ADDED
@@ -0,0 +1,101 @@
1
+ require 'sinatra'
2
+ require 'sinatra/config_file'
3
+ require 'projector'
4
+ require 'sinatra/json'
5
+ require 'thread'
6
+
7
+ class Sample < Sinatra::Base
8
+
9
+ register Sinatra::ConfigFile
10
+
11
+ config_file './config.yml'
12
+
13
+ Projector::Client.configure do |client|
14
+ client[:host] = settings.host
15
+ client[:token] = settings.token
16
+ client
17
+ end
18
+
19
+ # Forward an end user registration, i.e. from a mobile client device,
20
+ # to projector's register end user endpoint.
21
+ # Then, return the response data back to the requesting device.
22
+ # Here we're specifying two tags which Jane is part of.
23
+ post '/register_projector' do
24
+ # Implementer TODO: Handle authentication
25
+ # Extract user id and tags and insert into the
26
+ # end user object like below:
27
+ res = Projector.register_end_user({id: 'Jane', tags: ['likes dogs', 'eats_cheeseburgers']})
28
+ json res
29
+ end
30
+
31
+ # Deliver an event to the tags group called eats_cheeseburgers.
32
+ # Jane would receive this notification, as she's part of the eats_cheeseburgers
33
+ # tag set. Use the cheeseburger_alert event key.
34
+ post '/cheeseburger_alert' do
35
+ res = Projector.deliver_event({
36
+ target: {
37
+ tags: ['eats_cheeseburgers']
38
+ },
39
+ event_context: {
40
+ discount_price: 12,
41
+ restaurant_name: "Bill's Cheeseburgerz"
42
+ },
43
+ event_key: 'cheeseburger_alert'
44
+ })
45
+ json res
46
+ end
47
+
48
+ # Deliver an event specifically to Jane for the event_key cheeseburger_alert.
49
+ post '/alert_jane' do
50
+ res = Projector.deliver_event({
51
+ target: {
52
+ end_users: [{id: 'Jane'}]
53
+ },
54
+ event_context: {
55
+ discount_price: 12,
56
+ restaurant_name: "Bill's Cheeseburgerz"
57
+ },
58
+ event_key: 'cheeseburger_alert'
59
+ })
60
+ json res
61
+ end
62
+
63
+ # Example Application Logic:
64
+ # Our application delivers random discounts to
65
+ # registered end users on a timer using the Projector Node SDK.
66
+ @discounts = [
67
+ {
68
+ discount_price: 12,
69
+ restaurant_name: "Bill's Cheeseburgerz"
70
+ },
71
+ {
72
+ discount_price: 11,
73
+ restaurant_name: "Ted's Cheeseburger Shack"
74
+ },
75
+ {
76
+ discount_price: 14,
77
+ restaurant_name: "Joan of Arc's Cheesesteaks"
78
+ }
79
+ ]
80
+
81
+ # An example of custom logic which sends cheeseburger discounts
82
+ # to registered end users on a timer schedule.
83
+ Thread.new do
84
+ loop do
85
+ begin
86
+ disc = @discounts.sample
87
+ res = Projector.deliver_event({
88
+ target: {
89
+ tags: ['eats_cheeseburgers']
90
+ },
91
+ event_context: disc,
92
+ event_key: 'cheeseburger_alert'
93
+ })
94
+ puts "Successfully sent cheeseburger_alert #{res}"
95
+ rescue Error => e
96
+ puts e
97
+ end
98
+ sleep 10
99
+ end
100
+ end
101
+ end
metadata CHANGED
@@ -1,105 +1,88 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: projector
3
- version: !ruby/object:Gem::Version
4
- hash: 27
5
- prerelease: false
6
- segments:
7
- - 0
8
- - 1
9
- - 0
10
- version: 0.1.0
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
11
5
  platform: ruby
12
- authors:
13
- - Jay Zeschin
6
+ authors:
7
+ - Projector
14
8
  autorequire:
15
9
  bindir: bin
16
10
  cert_chain: []
17
-
18
- date: 2011-06-09 00:00:00 -06:00
19
- default_executable:
20
- dependencies:
21
- - !ruby/object:Gem::Dependency
22
- name: json
23
- prerelease: false
24
- requirement: &id001 !ruby/object:Gem::Requirement
25
- none: false
26
- requirements:
11
+ date: 2016-03-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: net-http-persistent
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.9'
27
20
  - - ">="
28
- - !ruby/object:Gem::Version
29
- hash: 3
30
- segments:
31
- - 0
32
- version: "0"
21
+ - !ruby/object:Gem::Version
22
+ version: 2.9.4
33
23
  type: :runtime
34
- version_requirements: *id001
35
- - !ruby/object:Gem::Dependency
36
- name: thor
37
24
  prerelease: false
38
- requirement: &id002 !ruby/object:Gem::Requirement
39
- none: false
40
- requirements:
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '2.9'
41
30
  - - ">="
42
- - !ruby/object:Gem::Version
43
- hash: 3
44
- segments:
45
- - 0
46
- version: "0"
47
- type: :runtime
48
- version_requirements: *id002
49
- description: Automatically keep a working directory with checkouts of all your Github projects
50
- email:
51
- - jay@zeschin.org
52
- executables:
53
- - projector
31
+ - !ruby/object:Gem::Version
32
+ version: 2.9.4
33
+ description: Create and manage users, devices, and events going to Projector.
34
+ email:
35
+ - engineering@projector.com
36
+ executables: []
54
37
  extensions: []
55
-
56
38
  extra_rdoc_files: []
57
-
58
- files:
59
- - .gitignore
39
+ files:
40
+ - ".gitignore"
60
41
  - Gemfile
42
+ - LICENSE
61
43
  - README.md
62
44
  - Rakefile
63
- - bin/projector
45
+ - bin/console
46
+ - bin/setup
47
+ - circle.yml
64
48
  - lib/projector.rb
65
- - lib/projector/cli.rb
66
- - lib/projector/helpers.rb
67
- - lib/projector/repo.rb
49
+ - lib/projector/api_methods.rb
50
+ - lib/projector/client.rb
51
+ - lib/projector/end_user.rb
52
+ - lib/projector/envelope.rb
53
+ - lib/projector/error.rb
54
+ - lib/projector/event.rb
55
+ - lib/projector/transport/http.rb
68
56
  - lib/projector/version.rb
69
57
  - projector.gemspec
70
- has_rdoc: true
71
- homepage: https://github.com/jayzes/projector
72
- licenses: []
73
-
58
+ - sample/Gemfile
59
+ - sample/Gemfile.lock
60
+ - sample/README.md
61
+ - sample/config.ru
62
+ - sample/config.yml
63
+ - sample/index.rb
64
+ homepage: https://www.projector.com
65
+ licenses:
66
+ - BSD-3-Clause
67
+ metadata: {}
74
68
  post_install_message:
75
69
  rdoc_options: []
76
-
77
- require_paths:
70
+ require_paths:
78
71
  - lib
79
- required_ruby_version: !ruby/object:Gem::Requirement
80
- none: false
81
- requirements:
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ requirements:
82
74
  - - ">="
83
- - !ruby/object:Gem::Version
84
- hash: 3
85
- segments:
86
- - 0
87
- version: "0"
88
- required_rubygems_version: !ruby/object:Gem::Requirement
89
- none: false
90
- requirements:
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ requirements:
91
79
  - - ">="
92
- - !ruby/object:Gem::Version
93
- hash: 3
94
- segments:
95
- - 0
96
- version: "0"
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
97
82
  requirements: []
98
-
99
83
  rubyforge_project:
100
- rubygems_version: 1.3.7
84
+ rubygems_version: 2.5.1
101
85
  signing_key:
102
- specification_version: 3
103
- summary: Automatically keep a working directory with checkouts of all your Github projects
86
+ specification_version: 4
87
+ summary: Ruby SDK for interacting with the Projector SDK
104
88
  test_files: []
105
-
data/bin/projector DELETED
@@ -1,17 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # -*- mode: ruby -*-
3
-
4
- # Requires
5
- begin
6
- require 'projector'
7
- rescue LoadError
8
- $:.unshift(File.dirname(__FILE__) + '/../lib') unless $:.include?(File.dirname(__FILE__) + '/../lib')
9
- require 'rubygems'
10
- require 'projector'
11
- end
12
-
13
- # Setup Constants
14
- Projector::GEM_ROOT = File.expand_path( File.dirname( __FILE__ ) + '/../' )
15
-
16
- # Run
17
- Projector::CLI.start
data/lib/projector/cli.rb DELETED
@@ -1,86 +0,0 @@
1
- require 'thor'
2
- require 'thor/actions'
3
-
4
- module Projector
5
-
6
- class CLI < Thor
7
-
8
- # Includes
9
- include Thor::Actions
10
- include Projector::Helpers
11
-
12
- # Tasks
13
- desc "check", "Check Git settings to make sure Projector can work its magic properly"
14
- long_desc <<-DESC
15
- Projector will check to make sure that your Git settings contain your Github username
16
- and API key (see http://help.github.com/set-your-user-name-email-and-github-token/)
17
- as well as your preferred working directory
18
- DESC
19
- def check
20
- unless github_username?
21
- say 'You need to configure your github.user setting - "git config --global github.user <username>" should do the trick'
22
- exit 1
23
- end
24
- unless github_token?
25
- say 'You need to configure your github.token setting - "git config --global github.token <token>" should do the trick'
26
- exit 1
27
- end
28
- unless projector_working_dir?
29
- say 'You need to configure your projector.workingdir setting - "git config --global projector.workingdir <directory>" should do the trick'
30
- exit 1
31
- end
32
- end
33
-
34
-
35
- desc "checkout", "Go through the Github repositories you can access and ensure that working copies for them are cloned into your working directory"
36
- long_desc <<-DESC
37
- Projector loops through all of the repositories that you can access and clones working
38
- copies of them under your configured working directory if they don't already exist
39
- DESC
40
- method_option :assume, :type => :boolean, :aliases => "-a"
41
- def checkout
42
- invoke :check
43
- repos = all_repos
44
- say "Checking #{repos.size} repositories"
45
- repos.each do |repo|
46
- say "Looking at #{repo.path}"
47
- if should_clone_repo?(repo, options[:assume])
48
- say "Cloning #{repo.clone_url} to #{repo.clone_path(projector_working_dir)}"
49
- repo.clone(projector_working_dir)
50
- end
51
- end
52
- end
53
-
54
- desc "update", "Go through the Github repositories you have in your working copy and see which ones need updating from origin"
55
- long_desc <<-DESC
56
- Projector loops through all of the repositories that you can access and updates the working
57
- copies of them under your configured working directory if they are outdated
58
- DESC
59
- method_option :assume, :type => :boolean, :aliases => "-a"
60
- def update
61
- invoke :check
62
- repos = all_repos
63
- say "Checking #{repos.size} repositories"
64
- repos.each do |repo|
65
- unless repo_cloned?(repo)
66
- say "Skipping #{repo.path} since it is not cloned"
67
- next
68
- end
69
- say "Looking at #{repo.path}"
70
- update_repo(repo) if repo_out_of_date?(repo) && (yes?("Repo #{repo.path} has remote changes. Update it? (y/n)") || options[:assume])
71
- end
72
- end
73
-
74
-
75
-
76
-
77
-
78
- desc "version", "Prints Projector's version information"
79
- def version
80
- say "Projector version #{Projector::VERSION}"
81
- end
82
- map %w(-v --version) => :version
83
-
84
- end
85
-
86
- end
@@ -1,76 +0,0 @@
1
- require "net/https"
2
- require "uri"
3
- require 'json'
4
-
5
- module Projector
6
- module Helpers
7
- def github_username
8
- `git config --get github.user`.strip
9
- end
10
-
11
- def github_username?
12
- github_username && github_username.length > 0
13
- end
14
-
15
- def github_token
16
- `git config --get github.token`.strip
17
- end
18
-
19
- def github_token?
20
- github_token && github_token.length > 0
21
- end
22
-
23
- def projector_working_dir
24
- `git config --get projector.workingdir`.strip
25
- end
26
-
27
- def projector_working_dir?
28
- projector_working_dir && projector_working_dir.length > 0
29
- end
30
-
31
- def repo_cloned?(repo)
32
- File.directory?(File.join(repo.clone_path(projector_working_dir), '.git'))
33
- end
34
-
35
- def repo_out_of_date?(repo)
36
- repo.fetch_refs(projector_working_dir)
37
- repo.local_head(projector_working_dir) != repo.remote_head(projector_working_dir)
38
- end
39
-
40
- def update_repo(repo)
41
- repo.update(projector_working_dir)
42
- end
43
-
44
- def should_clone_repo?(repo, assume_yes = false)
45
- !repo_cloned?(repo) && (assume_yes || yes?("Repo #{repo.path} is not cloned yet. Clone it? (y/n)"))
46
- end
47
-
48
- def user_repos
49
- repo_api_request("https://github.com/api/v2/json/repos/show/#{github_username}")
50
- end
51
-
52
- def organization_repos
53
- repo_api_request("https://github.com/api/v2/json/organizations/repositories?owned=1")
54
- end
55
-
56
- def collaborator_repos
57
- repo_api_request("https://github.com/api/v2/json/repos/pushable")
58
- end
59
-
60
- def all_repos
61
- (user_repos + organization_repos + collaborator_repos).uniq
62
- end
63
-
64
- def repo_api_request(endpoint)
65
- uri = URI.parse(endpoint)
66
- http = Net::HTTP.new(uri.host, uri.port)
67
- http.use_ssl = true
68
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE # TODO: http://www.rubyinside.com/how-to-cure-nethttps-risky-default-https-behavior-4010.html
69
- request = Net::HTTP::Get.new(uri.request_uri)
70
- request.basic_auth("#{github_username}/token", github_token)
71
- response = http.request(request)
72
- JSON.parse(response.body)['repositories'].map { |repo| Repo.new(repo['owner'], repo['name']) }
73
- end
74
-
75
- end
76
- end
@@ -1,34 +0,0 @@
1
- Repo = Struct.new(:owner, :name) do
2
- def path
3
- "#{self.owner}/#{self.name}"
4
- end
5
-
6
- def clone_path(base_dir)
7
- File.join(base_dir, self.owner, self.name)
8
- end
9
-
10
- def clone_url
11
- "git@github.com:#{self.owner}/#{self.name}.git"
12
- end
13
-
14
- def clone(base_dir)
15
- system("git clone #{clone_url} #{clone_path(base_dir)}")
16
- end
17
-
18
- def local_head(base_dir)
19
- `cd #{clone_path(base_dir)} && git rev-parse master`.strip
20
- end
21
-
22
- def remote_head(base_dir)
23
- `cd #{clone_path(base_dir)} && git rev-parse origin/master`.strip
24
- end
25
-
26
- def fetch_refs(base_dir)
27
- system("cd #{clone_path(base_dir)} && git fetch origin")
28
- end
29
-
30
- def update(base_dir)
31
- system("cd #{clone_path(base_dir)} && git pull --rebase origin master")
32
- end
33
-
34
- end