Permalink
Browse files

Initial Commit

  • Loading branch information...
0 parents commit d5777fbce408e96e385fd4de39c66b38f4fd15c7 @mattt committed Nov 28, 2012
Showing with 381 additions and 0 deletions.
  1. +3 −0 Gemfile
  2. +36 −0 Gemfile.lock
  3. +19 −0 LICENSE
  4. +77 −0 README.md
  5. +10 −0 Rakefile
  6. +7 −0 example/Gemfile
  7. +41 −0 example/Gemfile.lock
  8. +1 −0 example/Procfile
  9. +13 −0 example/README.md
  10. +8 −0 example/config.ru
  11. +134 −0 lib/rack/push-notification.rb
  12. +5 −0 lib/rack/push-notification/version.rb
  13. +27 −0 rack-push-notification.gemspec
3 Gemfile
@@ -0,0 +1,3 @@
+source :rubygems
+
+gemspec
36 Gemfile.lock
@@ -0,0 +1,36 @@
+PATH
+ remote: .
+ specs:
+ rack-push-notification (0.0.1)
+ rack (~> 1.4)
+ rack-contrib (~> 1.1.0)
+ sequel (~> 3.37.0)
+ sinatra (~> 1.3.2)
+ sinatra-param (~> 0.1.1)
+
+GEM
+ remote: http://rubygems.org/
+ specs:
+ rack (1.4.1)
+ rack-contrib (1.1.0)
+ rack (>= 0.9.1)
+ rack-protection (1.2.0)
+ rack
+ rake (0.9.2.2)
+ rspec (0.6.4)
+ sequel (3.37.0)
+ sinatra (1.3.3)
+ rack (~> 1.3, >= 1.3.6)
+ rack-protection (~> 1.2)
+ tilt (~> 1.3, >= 1.3.3)
+ sinatra-param (0.1.1)
+ sinatra (~> 1.3)
+ tilt (1.3.3)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ rack-push-notification!
+ rake (~> 0.9.2)
+ rspec (~> 0.6.1)
19 LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2012 Mattt Thompson (http://mattt.me/)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
77 README.md
@@ -0,0 +1,77 @@
+Rack::PushNotification
+======================
+**An extensible, Rack-mountable webservice for managing push notification information**
+
+There is misconception that managing push notification information is a difficult problem. It's really not.
+
+This library is generates a `/devices` API endpoint, that can be used by iOS apps to register and unregister for push notifications.
+
+## Example Record
+
+<table>
+ <tr><td><tt>token</tt></td><td>"ce8be627 2e43e855 16033e24 b4c28922 0eeda487 9c477160 b2545e95 b68b5969"</td></tr>
+ <tr><td><tt>alias</tt></td><td>mattt@heroku.com</td></tr>
+ <tr><td><tt>badge</tt></td><td>0</td></tr>
+ <tr><td><tt>locale</tt></td><td>en_US</td></tr>
+ <tr><td><tt>language</tt></td><td>en</td></tr>
+ <tr><td><tt>timezone</tt></td><td>America/Los_Angeles</td></tr>
+ <tr><td><tt>ip_address</tt></td><td>0.0.0.0</td></tr>
+ <tr><td><tt>lat</tt></td><td>37.7716</td></tr>
+ <tr><td><tt>lng</tt></td><td>-122.4137</td></tr>
+ <tr><td><tt>tags</tt></td><td><tt>["iPhone OS 6.0", "v1.0", "iPhone"]</tt></td></tr>
+</table>
+
+Each device has a `token`, which uniquely identifies the app installation on a particular device. This token can be associated with an `alias`, which can be a domain-specific piece of identifying information, such as a username or e-mail address. A running `badge` count is used to keep track of the badge count to show on the app icon.
+
+A device's `locale` & `language` can be used to localize outgoing communications to that particular user. Having `timezone` information gives you the ability to schedule messages for an exact time of day, to ensure maximum impact (and minimum annoyance). `ip_address` as well as `lat` and `lng` allows you to specifically target users according to their geographic location.
+
+> It is recommended that you use `Rack::PushNotification` in conjunction with some sort of Rack authentication middleware, so that the registration endpoints are not accessible without some form of credentials.
+
+## Example Usage
+
+Rack::PushNotification can be run as Rack middleware or as a single web application. All that is required is a connection to a Postgres database.
+
+### config.ru
+
+```ruby
+require 'bundler'
+Bundler.require
+
+DB = Sequel.connect(ENV['DATABASE_URL'])
+
+run Rack::PushNotification
+```
+
+An example application can be found in the `/example` directory of this repository.
+
+## iOS Client Library
+
+To get the full benefit of `Rack::PushNotification`, use the [Orbiter](https://github.com/mattt/Orbiter) library to register for Push Notifications on iOS.
+
+```objective-c
+#import "Orbiter.h"
+
+- (void)application:(UIApplication *)application
+didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
+{
+ NSURL *serverURL = [NSURL URLWithString:@"http://raging-notification-3556.herokuapp.com/"]
+ Orbiter *orbiter = [[Orbiter alloc] initWithBaseURL:serverURL credential:nil];
+ [orbiter registerDeviceToken:deviceToken withAlias:nil success:^(id responseObject) {
+ NSLog(@"Registration Success: %@", responseObject);
+ } failure:^(NSError *error) {
+ NSLog(@"Registration Error: %@", error);
+ }];
+}
+```
+
+## Contact
+
+Mattt Thompson
+
+- http://github.com/mattt
+- http://twitter.com/mattt
+- m@mattt.me
+
+## License
+
+Rack::PushNotification is available under the MIT license. See the LICENSE file for more info.
10 Rakefile
@@ -0,0 +1,10 @@
+require "bundler"
+Bundler.setup
+
+gemspec = eval(File.read("rack-push-notification.gemspec"))
+
+task :build => "#{gemspec.full_name}.gem"
+
+file "#{gemspec.full_name}.gem" => gemspec.files + ["rack-push-notification.gemspec"] do
+ system "gem build rack-push-notification.gemspec"
+end
7 example/Gemfile
@@ -0,0 +1,7 @@
+source :rubygems
+
+gem 'rack-push-notification', :require => 'rack/push-notification', :path => File.join(__FILE__, "../..")
+
+gem 'pg'
+
+gem 'thin'
41 example/Gemfile.lock
@@ -0,0 +1,41 @@
+PATH
+ remote: /Users/mattt/Code/Ruby/rack-push-notification
+ specs:
+ rack-push-notification (0.0.1)
+ rack (~> 1.4)
+ rack-contrib (~> 1.1.0)
+ sequel (~> 3.37.0)
+ sinatra (~> 1.3.2)
+ sinatra-param (~> 0.1.1)
+
+GEM
+ remote: http://rubygems.org/
+ specs:
+ daemons (1.1.9)
+ eventmachine (1.0.0)
+ pg (0.14.1)
+ rack (1.4.1)
+ rack-contrib (1.1.0)
+ rack (>= 0.9.1)
+ rack-protection (1.2.0)
+ rack
+ sequel (3.37.0)
+ sinatra (1.3.3)
+ rack (~> 1.3, >= 1.3.6)
+ rack-protection (~> 1.2)
+ tilt (~> 1.3, >= 1.3.3)
+ sinatra-param (0.1.1)
+ sinatra (~> 1.3)
+ thin (1.5.0)
+ daemons (>= 1.0.9)
+ eventmachine (>= 0.12.6)
+ rack (>= 1.0.0)
+ tilt (1.3.3)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ pg
+ rack-push-notification!
+ thin
1 example/Procfile
@@ -0,0 +1 @@
+web: bundle exec thin start -p $PORT
13 example/README.md
@@ -0,0 +1,13 @@
+# Rack::PushNotification Example
+
+## Instructions
+
+To run the example application, ensure that you have Postgres running locally (see [Postgres.app](http://postgresapp.com) for an easy way to get set up on a Mac), and run the following commands:
+
+```sh
+$ cd example
+$ psql -c "CREATE DATABASE push_notification;"
+$ echo "DATABASE_URL=postgres://localhost:5432/notification" > .env
+$ bundle
+$ foreman start
+```
8 example/config.ru
@@ -0,0 +1,8 @@
+require 'bundler'
+Bundler.require
+
+STDOUT.sync = true
+
+DB = Sequel.connect(ENV['DATABASE_URL'] || "postgres://localhost:5432/push_notification")
+
+run Rack::PushNotification
134 lib/rack/push-notification.rb
@@ -0,0 +1,134 @@
+require 'rack'
+require 'rack/contrib'
+
+require 'sinatra/base'
+require 'sinatra/param'
+
+require 'sequel'
+
+require 'rack/push-notification/version'
+
+Sequel.extension(:pg_array)
+
+module Rack::PushNotification
+end
+
+module Rack
+ def self.PushNotification(options = {})
+ klass = Rack::PushNotification.const_set("Device", Class.new(Sequel::Model))
+ klass.dataset = :devices
+
+ klass.class_eval do
+ self.strict_param_setting = false
+ self.raise_on_save_failure = false
+
+ plugin :json_serializer, naked: true, except: :id
+ plugin :validation_helpers
+ plugin :timestamps, force: true
+ plugin :schema
+
+ set_schema do
+ primary_key :id
+
+ column :token, :varchar, null: false, unique: true
+ column :alias, :varchar
+ column :badge, :int4, null: false, default: 0
+ column :locale, :varchar
+ column :language, :varchar
+ column :timezone, :varchar, null: false, default: 'UTC'
+ column :ip_address, :inet
+ column :lat, :float8
+ column :lng, :float8
+ column :tags, :'text[]'
+
+ index :token
+ index :alias
+ index [:lat, :lng]
+ end
+
+ create_table unless table_exists?
+
+ def before_validation
+ normalize_token!
+ end
+
+ private
+
+ def normalize_token!
+ self.token = self.token.strip.gsub(/[<\s>]/, '')
+ end
+ end
+
+ app = Class.new(Sinatra::Base) do
+ use Rack::PostBodyContentTypeParser
+ helpers Sinatra::Param
+
+ disable :raise_errors, :show_exceptions
+
+ before do
+ content_type :json
+ end
+
+ get '/devices/?' do
+ param :languages, Array
+ param :tags, Array
+
+ @devices = klass.dataset
+ [:alias, :badge, :locale, :languages, :timezone, :tags].each do |attribute|
+ @devices = @devices.filter(attribute => params[attribute]) if params[attribute]
+ end
+
+ @devices.to_json
+ end
+
+ put '/devices/:token/?' do
+ param :languages, Array
+ param :tags, Array
+
+ @record = klass.new(params)
+ @record.tags = nil
+ if @record.save
+ status 201
+ @record.to_json
+ else
+ status 406
+ {errors: @record.errors}.to_json
+ end
+ end
+
+ get '/devices/:token/?' do
+ @record = klass.find(token: params[:token])
+ if @record
+ @record.to_json
+ else
+ status 404
+ end
+ end
+
+ delete '/devices/:token/?' do
+ @record = klass.find(token: params[:token]) or halt 404
+ if @record.destroy
+ status 200
+ else
+ status 406
+ {errors: record.errors}.to_json
+ end
+ end
+ end
+
+ return app
+ end
+
+ module PushNotification
+ class << self
+ def new(options = {})
+ @app ||= ::Rack::PushNotification()
+ end
+
+ def call(*args)
+ @app ||= ::Rack::PushNotification()
+ @app.call(*args)
+ end
+ end
+ end
+end
5 lib/rack/push-notification/version.rb
@@ -0,0 +1,5 @@
+module Rack
+ module PushNotification
+ VERSION = '0.0.1'
+ end
+end
27 rack-push-notification.gemspec
@@ -0,0 +1,27 @@
+# -*- encoding: utf-8 -*-
+$:.push File.expand_path("../lib", __FILE__)
+require "rack/push-notification"
+
+Gem::Specification.new do |s|
+ s.name = "rack-push-notification"
+ s.authors = ["Mattt Thompson"]
+ s.email = "m@mattt.me"
+ s.homepage = "http://mattt.me"
+ s.version = Rack::PushNotification::VERSION
+ s.platform = Gem::Platform::RUBY
+ s.summary = "Rack::PushNotification"
+ s.description = "Generate a REST API for registering and querying push notification device tokens."
+ s.add_development_dependency "rspec", "~> 0.6.1"
+ s.add_development_dependency "rake", "~> 0.9.2"
+
+ s.add_dependency "rack", "~> 1.4"
+ s.add_dependency "rack-contrib", "~> 1.1.0"
+ s.add_dependency "sinatra", "~> 1.3.2"
+ s.add_dependency "sinatra-param", "~> 0.1.1"
+ s.add_dependency "sequel", "~> 3.37.0"
+
+ s.files = Dir["./**/*"].reject { |file| file =~ /\.\/(bin|example|log|pkg|script|spec|test|vendor)/ }
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
+ s.require_paths = ["lib"]
+end

0 comments on commit d5777fb

Please sign in to comment.