presskit-apn_on_rails 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.rspec +2 -0
- data/.specification +80 -0
- data/Gemfile +19 -0
- data/Gemfile.lock +47 -0
- data/LICENSE +21 -0
- data/README +179 -0
- data/README.textile +209 -0
- data/Rakefile +50 -0
- data/VERSION +1 -0
- data/autotest/discover.rb +1 -0
- data/generators/apn_migrations_generator.rb +31 -0
- data/generators/templates/apn_migrations/001_create_apn_devices.rb +13 -0
- data/generators/templates/apn_migrations/002_create_apn_notifications.rb +23 -0
- data/generators/templates/apn_migrations/003_alter_apn_devices.rb +25 -0
- data/generators/templates/apn_migrations/004_create_apn_apps.rb +18 -0
- data/generators/templates/apn_migrations/005_create_groups.rb +23 -0
- data/generators/templates/apn_migrations/006_alter_apn_groups.rb +11 -0
- data/generators/templates/apn_migrations/007_create_device_groups.rb +27 -0
- data/generators/templates/apn_migrations/008_create_apn_group_notifications.rb +23 -0
- data/generators/templates/apn_migrations/009_create_pull_notifications.rb +16 -0
- data/generators/templates/apn_migrations/010_alter_apn_notifications.rb +21 -0
- data/generators/templates/apn_migrations/011_make_device_token_index_nonunique.rb +11 -0
- data/generators/templates/apn_migrations/012_add_launch_notification_to_apn_pull_notifications.rb +9 -0
- data/lib/apn_on_rails.rb +4 -0
- data/lib/apn_on_rails/apn_on_rails.rb +81 -0
- data/lib/apn_on_rails/app/models/apn/app.rb +152 -0
- data/lib/apn_on_rails/app/models/apn/base.rb +9 -0
- data/lib/apn_on_rails/app/models/apn/device.rb +50 -0
- data/lib/apn_on_rails/app/models/apn/device_grouping.rb +16 -0
- data/lib/apn_on_rails/app/models/apn/group.rb +12 -0
- data/lib/apn_on_rails/app/models/apn/group_notification.rb +79 -0
- data/lib/apn_on_rails/app/models/apn/notification.rb +93 -0
- data/lib/apn_on_rails/app/models/apn/pull_notification.rb +28 -0
- data/lib/apn_on_rails/libs/connection.rb +70 -0
- data/lib/apn_on_rails/libs/feedback.rb +39 -0
- data/lib/apn_on_rails/tasks/apn.rake +30 -0
- data/lib/apn_on_rails/tasks/db.rake +19 -0
- data/lib/apn_on_rails_tasks.rb +3 -0
- data/presskit-apn_on_rails.gemspec +144 -0
- data/spec/active_record/setup_ar.rb +19 -0
- data/spec/apn_on_rails/app/models/apn/app_spec.rb +230 -0
- data/spec/apn_on_rails/app/models/apn/device_spec.rb +60 -0
- data/spec/apn_on_rails/app/models/apn/group_notification_spec.rb +66 -0
- data/spec/apn_on_rails/app/models/apn/notification_spec.rb +71 -0
- data/spec/apn_on_rails/app/models/apn/pull_notification_spec.rb +100 -0
- data/spec/apn_on_rails/libs/connection_spec.rb +40 -0
- data/spec/apn_on_rails/libs/feedback_spec.rb +43 -0
- data/spec/extensions/string.rb +10 -0
- data/spec/factories/app_factory.rb +27 -0
- data/spec/factories/device_factory.rb +29 -0
- data/spec/factories/device_grouping_factory.rb +22 -0
- data/spec/factories/group_factory.rb +27 -0
- data/spec/factories/group_notification_factory.rb +22 -0
- data/spec/factories/notification_factory.rb +22 -0
- data/spec/factories/pull_notification_factory.rb +22 -0
- data/spec/fixtures/hexa.bin +1 -0
- data/spec/fixtures/message_for_sending.bin +0 -0
- data/spec/rails_root/config/apple_push_notification_development.pem +19 -0
- data/spec/spec_helper.rb +55 -0
- metadata +282 -0
@@ -0,0 +1,50 @@
|
|
1
|
+
# Represents an iPhone (or other APN enabled device).
|
2
|
+
# An APN::Device can have many APN::Notification.
|
3
|
+
#
|
4
|
+
# In order for the APN::Feedback system to work properly you *MUST*
|
5
|
+
# touch the <tt>last_registered_at</tt> column everytime someone opens
|
6
|
+
# your application. If you do not, then it is possible, and probably likely,
|
7
|
+
# that their device will be removed and will no longer receive notifications.
|
8
|
+
#
|
9
|
+
# Example:
|
10
|
+
# Device.create(:token => '5gxadhy6 6zmtxfl6 5zpbcxmw ez3w7ksf qscpr55t trknkzap 7yyt45sc g6jrw7qz')
|
11
|
+
class APN::Device < APN::Base
|
12
|
+
|
13
|
+
belongs_to :app, :class_name => 'APN::App'
|
14
|
+
has_many :notifications, :class_name => 'APN::Notification'
|
15
|
+
has_many :unsent_notifications, :class_name => 'APN::Notification', :conditions => 'sent_at is null'
|
16
|
+
belongs_to :user
|
17
|
+
|
18
|
+
validates_uniqueness_of :token, :scope => :artist_id
|
19
|
+
validates_format_of :token, :with => /^[a-z0-9]{8}\s[a-z0-9]{8}\s[a-z0-9]{8}\s[a-z0-9]{8}\s[a-z0-9]{8}\s[a-z0-9]{8}\s[a-z0-9]{8}\s[a-z0-9]{8}$/
|
20
|
+
|
21
|
+
before_create :set_last_registered_at
|
22
|
+
|
23
|
+
# The <tt>feedback_at</tt> accessor is set when the
|
24
|
+
# device is marked as potentially disconnected from your
|
25
|
+
# application by Apple.
|
26
|
+
attr_accessor :feedback_at
|
27
|
+
|
28
|
+
# Stores the token (Apple's device ID) of the iPhone (device).
|
29
|
+
#
|
30
|
+
# If the token comes in like this:
|
31
|
+
# '<5gxadhy6 6zmtxfl6 5zpbcxmw ez3w7ksf qscpr55t trknkzap 7yyt45sc g6jrw7qz>'
|
32
|
+
# Then the '<' and '>' will be stripped off.
|
33
|
+
def token=(token)
|
34
|
+
res = token.scan(/\<(.+)\>/).first
|
35
|
+
unless res.nil? || res.empty?
|
36
|
+
token = res.first
|
37
|
+
end
|
38
|
+
write_attribute('token', token)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns the hexadecimal representation of the device's token.
|
42
|
+
def to_hexa
|
43
|
+
[self.token.delete(' ')].pack('H*')
|
44
|
+
end
|
45
|
+
|
46
|
+
def set_last_registered_at
|
47
|
+
self.last_registered_at = Time.now #if self.last_registered_at.nil?
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class APN::DeviceGrouping < APN::Base
|
2
|
+
|
3
|
+
belongs_to :group, :class_name => 'APN::Group'
|
4
|
+
belongs_to :device, :class_name => 'APN::Device'
|
5
|
+
|
6
|
+
validates_presence_of :device_id, :group_id
|
7
|
+
validate :same_app_id
|
8
|
+
validates_uniqueness_of :device_id, :scope => :group_id
|
9
|
+
|
10
|
+
def same_app_id
|
11
|
+
unless self.group and self.device and self.group.app_id == self.device.app_id
|
12
|
+
errors.add_to_base("device and group must belong to the same app")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class APN::Group < APN::Base
|
2
|
+
|
3
|
+
belongs_to :app, :class_name => 'APN::App'
|
4
|
+
has_many :device_groupings, :class_name => "APN::DeviceGrouping", :dependent => :destroy
|
5
|
+
has_many :devices, :class_name => 'APN::Device', :through => :device_groupings
|
6
|
+
has_many :group_notifications, :class_name => 'APN::GroupNotification'
|
7
|
+
has_many :unsent_group_notifications, :class_name => 'APN::GroupNotification', :conditions => 'sent_at is null'
|
8
|
+
|
9
|
+
validates_presence_of :app_id
|
10
|
+
validates_uniqueness_of :name, :scope => :app_id
|
11
|
+
|
12
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
class APN::GroupNotification < APN::Base
|
2
|
+
include ::ActionView::Helpers::TextHelper
|
3
|
+
extend ::ActionView::Helpers::TextHelper
|
4
|
+
serialize :custom_properties
|
5
|
+
|
6
|
+
belongs_to :group, :class_name => 'APN::Group'
|
7
|
+
has_one :app, :class_name => 'APN::App', :through => :group
|
8
|
+
has_many :device_groupings, :through => :group
|
9
|
+
|
10
|
+
validates_presence_of :group_id
|
11
|
+
|
12
|
+
def devices
|
13
|
+
self.group.devices
|
14
|
+
end
|
15
|
+
|
16
|
+
# Stores the text alert message you want to send to the device.
|
17
|
+
#
|
18
|
+
# If the message is over 150 characters long it will get truncated
|
19
|
+
# to 150 characters with a <tt>...</tt>
|
20
|
+
def alert=(message)
|
21
|
+
if !message.blank? && message.size > 150
|
22
|
+
message = truncate(message, :length => 150)
|
23
|
+
end
|
24
|
+
write_attribute('alert', message)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Creates a Hash that will be the payload of an APN.
|
28
|
+
#
|
29
|
+
# Example:
|
30
|
+
# apn = APN::GroupNotification.new
|
31
|
+
# apn.badge = 5
|
32
|
+
# apn.sound = 'my_sound.aiff'
|
33
|
+
# apn.alert = 'Hello!'
|
34
|
+
# apn.apple_hash # => {"aps" => {"badge" => 5, "sound" => "my_sound.aiff", "alert" => "Hello!"}}
|
35
|
+
#
|
36
|
+
# Example 2:
|
37
|
+
# apn = APN::GroupNotification.new
|
38
|
+
# apn.badge = 0
|
39
|
+
# apn.sound = true
|
40
|
+
# apn.custom_properties = {"typ" => 1}
|
41
|
+
# apn.apple_hash # => {"aps" => {"badge" => 0, "sound" => 1.aiff},"typ" => "1"}
|
42
|
+
def apple_hash
|
43
|
+
result = {}
|
44
|
+
result['aps'] = {}
|
45
|
+
result['aps']['alert'] = self.alert if self.alert
|
46
|
+
result['aps']['badge'] = self.badge.to_i if self.badge
|
47
|
+
if self.sound
|
48
|
+
result['aps']['sound'] = self.sound if self.sound.is_a? String
|
49
|
+
result['aps']['sound'] = "1.aiff" if self.sound.is_a?(TrueClass)
|
50
|
+
end
|
51
|
+
if self.custom_properties
|
52
|
+
self.custom_properties.each do |key,value|
|
53
|
+
result["#{key}"] = "#{value}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
result
|
57
|
+
end
|
58
|
+
|
59
|
+
# Creates the JSON string required for an APN message.
|
60
|
+
#
|
61
|
+
# Example:
|
62
|
+
# apn = APN::Notification.new
|
63
|
+
# apn.badge = 5
|
64
|
+
# apn.sound = 'my_sound.aiff'
|
65
|
+
# apn.alert = 'Hello!'
|
66
|
+
# apn.to_apple_json # => '{"aps":{"badge":5,"sound":"my_sound.aiff","alert":"Hello!"}}'
|
67
|
+
def to_apple_json
|
68
|
+
self.apple_hash.to_json
|
69
|
+
end
|
70
|
+
|
71
|
+
# Creates the binary message needed to send to Apple.
|
72
|
+
def message_for_sending(device)
|
73
|
+
json = self.to_apple_json
|
74
|
+
message = "\0\0 #{device.to_hexa}\0#{json.length.chr}#{json}"
|
75
|
+
raise APN::Errors::ExceededMessageSizeError.new(message) if message.size.to_i > 256
|
76
|
+
message
|
77
|
+
end
|
78
|
+
|
79
|
+
end # APN::Notification
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# Represents the message you wish to send.
|
2
|
+
# An APN::Notification belongs to an APN::Device.
|
3
|
+
#
|
4
|
+
# Example:
|
5
|
+
# apn = APN::Notification.new
|
6
|
+
# apn.badge = 5
|
7
|
+
# apn.sound = 'my_sound.aiff'
|
8
|
+
# apn.alert = 'Hello!'
|
9
|
+
# apn.device = APN::Device.find(1)
|
10
|
+
# apn.save
|
11
|
+
#
|
12
|
+
# To deliver call the following method:
|
13
|
+
# APN::Notification.send_notifications
|
14
|
+
#
|
15
|
+
# As each APN::Notification is sent the <tt>sent_at</tt> column will be timestamped,
|
16
|
+
# so as to not be sent again.
|
17
|
+
class APN::Notification < APN::Base
|
18
|
+
include ::ActionView::Helpers::TextHelper
|
19
|
+
extend ::ActionView::Helpers::TextHelper
|
20
|
+
serialize :custom_properties
|
21
|
+
|
22
|
+
belongs_to :device, :class_name => 'APN::Device'
|
23
|
+
has_one :app, :class_name => 'APN::App', :through => :device
|
24
|
+
|
25
|
+
# Stores the text alert message you want to send to the device.
|
26
|
+
#
|
27
|
+
# If the message is over 150 characters long it will get truncated
|
28
|
+
# to 150 characters with a <tt>...</tt>
|
29
|
+
def alert=(message)
|
30
|
+
if !message.blank? && message.size > 150
|
31
|
+
message = truncate(message, :length => 150)
|
32
|
+
end
|
33
|
+
write_attribute('alert', message)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Creates a Hash that will be the payload of an APN.
|
37
|
+
#
|
38
|
+
# Example:
|
39
|
+
# apn = APN::Notification.new
|
40
|
+
# apn.badge = 5
|
41
|
+
# apn.sound = 'my_sound.aiff'
|
42
|
+
# apn.alert = 'Hello!'
|
43
|
+
# apn.apple_hash # => {"aps" => {"badge" => 5, "sound" => "my_sound.aiff", "alert" => "Hello!"}}
|
44
|
+
#
|
45
|
+
# Example 2:
|
46
|
+
# apn = APN::Notification.new
|
47
|
+
# apn.badge = 0
|
48
|
+
# apn.sound = true
|
49
|
+
# apn.custom_properties = {"typ" => 1}
|
50
|
+
# apn.apple_hash # => {"aps" => {"badge" => 0, "sound" => "1.aiff"}, "typ" => "1"}
|
51
|
+
def apple_hash
|
52
|
+
result = {}
|
53
|
+
result['aps'] = {}
|
54
|
+
result['aps']['alert'] = self.alert if self.alert
|
55
|
+
result['aps']['badge'] = self.badge.to_i if self.badge
|
56
|
+
if self.sound
|
57
|
+
result['aps']['sound'] = self.sound if self.sound.is_a? String
|
58
|
+
result['aps']['sound'] = "1.aiff" if self.sound.is_a?(TrueClass)
|
59
|
+
end
|
60
|
+
if self.custom_properties
|
61
|
+
self.custom_properties.each do |key,value|
|
62
|
+
result["#{key}"] = "#{value}"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
result
|
66
|
+
end
|
67
|
+
|
68
|
+
# Creates the JSON string required for an APN message.
|
69
|
+
#
|
70
|
+
# Example:
|
71
|
+
# apn = APN::Notification.new
|
72
|
+
# apn.badge = 5
|
73
|
+
# apn.sound = 'my_sound.aiff'
|
74
|
+
# apn.alert = 'Hello!'
|
75
|
+
# apn.to_apple_json # => '{"aps":{"badge":5,"sound":"my_sound.aiff","alert":"Hello!"}}'
|
76
|
+
def to_apple_json
|
77
|
+
self.apple_hash.to_json
|
78
|
+
end
|
79
|
+
|
80
|
+
# Creates the binary message needed to send to Apple.
|
81
|
+
def message_for_sending
|
82
|
+
json = self.to_apple_json
|
83
|
+
message = "\0\0 #{self.device.to_hexa}\0#{json.length.chr}#{json}"
|
84
|
+
raise APN::Errors::ExceededMessageSizeError.new(message) if message.size.to_i > 256
|
85
|
+
message
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.send_notifications
|
89
|
+
ActiveSupport::Deprecation.warn("The method APN::Notification.send_notifications is deprecated. Use APN::App.send_notifications instead.")
|
90
|
+
APN::App.send_notifications
|
91
|
+
end
|
92
|
+
|
93
|
+
end # APN::Notification
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class APN::PullNotification < APN::Base
|
2
|
+
belongs_to :app, :class_name => 'APN::App'
|
3
|
+
|
4
|
+
validates_presence_of :app_id
|
5
|
+
|
6
|
+
def self.latest_since(app_id, since_date=nil)
|
7
|
+
if since_date
|
8
|
+
res = first(:order => "created_at DESC",
|
9
|
+
:conditions => ["app_id = ? AND created_at > ? AND launch_notification = ?", app_id, since_date, false])
|
10
|
+
else
|
11
|
+
res = first(:order => "created_at DESC",
|
12
|
+
:conditions => ["app_id = ? AND launch_notification = ?", app_id, true])
|
13
|
+
res = first(:order => "created_at DESC",
|
14
|
+
:conditions => ["app_id = ? AND launch_notification = ?", app_id, false]) unless res
|
15
|
+
end
|
16
|
+
res
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.all_since(app_id, since_date=nil)
|
20
|
+
if since_date
|
21
|
+
res = all(:order => "created_at DESC",
|
22
|
+
:conditions => ["app_id = ? AND created_at > ? AND launch_notification = ?", app_id, since_date, false])
|
23
|
+
else
|
24
|
+
res = all(:order => "created_at DESC",
|
25
|
+
:conditions => ["app_id = ? AND launch_notification = ?", app_id, false])
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module APN
|
2
|
+
module Connection
|
3
|
+
|
4
|
+
class << self
|
5
|
+
|
6
|
+
# Yields up an SSL socket to write notifications to.
|
7
|
+
# The connections are close automatically.
|
8
|
+
#
|
9
|
+
# Example:
|
10
|
+
# APN::Configuration.open_for_delivery do |conn|
|
11
|
+
# conn.write('my cool notification')
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# Configuration parameters are:
|
15
|
+
#
|
16
|
+
# configatron.apn.passphrase = ''
|
17
|
+
# configatron.apn.port = 2195
|
18
|
+
# configatron.apn.host = 'gateway.sandbox.push.apple.com' # Development
|
19
|
+
# configatron.apn.host = 'gateway.push.apple.com' # Production
|
20
|
+
# configatron.apn.cert = File.join(rails_root, 'config', 'apple_push_notification_development.pem')) # Development
|
21
|
+
# configatron.apn.cert = File.join(rails_root, 'config', 'apple_push_notification_production.pem')) # Production
|
22
|
+
def open_for_delivery(options = {}, &block)
|
23
|
+
open(options, &block)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Yields up an SSL socket to receive feedback from.
|
27
|
+
# The connections are close automatically.
|
28
|
+
# Configuration parameters are:
|
29
|
+
#
|
30
|
+
# configatron.apn.feedback.passphrase = ''
|
31
|
+
# configatron.apn.feedback.port = 2196
|
32
|
+
# configatron.apn.feedback.host = 'feedback.sandbox.push.apple.com' # Development
|
33
|
+
# configatron.apn.feedback.host = 'feedback.push.apple.com' # Production
|
34
|
+
# configatron.apn.feedback.cert = File.join(rails_root, 'config', 'apple_push_notification_development.pem')) # Development
|
35
|
+
# configatron.apn.feedback.cert = File.join(rails_root, 'config', 'apple_push_notification_production.pem')) # Production
|
36
|
+
def open_for_feedback(options = {}, &block)
|
37
|
+
options = {:cert => configatron.apn.feedback.cert,
|
38
|
+
:passphrase => configatron.apn.feedback.passphrase,
|
39
|
+
:host => configatron.apn.feedback.host,
|
40
|
+
:port => configatron.apn.feedback.port}.merge(options)
|
41
|
+
open(options, &block)
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
def open(options = {}, &block) # :nodoc:
|
46
|
+
options = {:cert => configatron.apn.cert,
|
47
|
+
:passphrase => configatron.apn.passphrase,
|
48
|
+
:host => configatron.apn.host,
|
49
|
+
:port => configatron.apn.port}.merge(options)
|
50
|
+
#cert = File.read(options[:cert])
|
51
|
+
cert = options[:cert]
|
52
|
+
ctx = OpenSSL::SSL::SSLContext.new
|
53
|
+
ctx.key = OpenSSL::PKey::RSA.new(cert, options[:passphrase])
|
54
|
+
ctx.cert = OpenSSL::X509::Certificate.new(cert)
|
55
|
+
|
56
|
+
sock = TCPSocket.new(options[:host], options[:port])
|
57
|
+
ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
|
58
|
+
ssl.sync = true
|
59
|
+
ssl.connect
|
60
|
+
|
61
|
+
yield ssl, sock if block_given?
|
62
|
+
ensure
|
63
|
+
ssl.close
|
64
|
+
sock.close
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
end # Connection
|
70
|
+
end # APN
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module APN
|
2
|
+
# Module for talking to the Apple Feedback Service.
|
3
|
+
# The service is meant to let you know when a device is no longer
|
4
|
+
# registered to receive notifications for your application.
|
5
|
+
module Feedback
|
6
|
+
|
7
|
+
class << self
|
8
|
+
|
9
|
+
# Returns an Array of APN::Device objects that
|
10
|
+
# has received feedback from Apple. Each APN::Device will
|
11
|
+
# have it's <tt>feedback_at</tt> accessor marked with the time
|
12
|
+
# that Apple believes the device de-registered itself.
|
13
|
+
def devices(cert, &block)
|
14
|
+
devices = []
|
15
|
+
return if cert.nil?
|
16
|
+
APN::Connection.open_for_feedback({:cert => cert}) do |conn, sock|
|
17
|
+
while line = conn.read(38) # Read 38 bytes from the SSL socket
|
18
|
+
feedback = line.unpack('N1n1H140')
|
19
|
+
token = feedback[2].scan(/.{0,8}/).join(' ').strip
|
20
|
+
device = APN::Device.find(:first, :conditions => {:token => token})
|
21
|
+
if device
|
22
|
+
device.feedback_at = Time.at(feedback[0])
|
23
|
+
devices << device
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
devices.each(&block) if block_given?
|
28
|
+
return devices
|
29
|
+
end # devices
|
30
|
+
|
31
|
+
def process_devices
|
32
|
+
ActiveSupport::Deprecation.warn("The method APN::Feedback.process_devices is deprecated. Use APN::App.process_devices instead.")
|
33
|
+
APN::App.process_devices
|
34
|
+
end
|
35
|
+
|
36
|
+
end # class << self
|
37
|
+
|
38
|
+
end # Feedback
|
39
|
+
end # APN
|
@@ -0,0 +1,30 @@
|
|
1
|
+
namespace :apn do
|
2
|
+
|
3
|
+
namespace :notifications do
|
4
|
+
|
5
|
+
desc "Deliver all unsent APN notifications."
|
6
|
+
task :deliver => [:environment] do
|
7
|
+
APN::App.send_notifications
|
8
|
+
end
|
9
|
+
|
10
|
+
end # notifications
|
11
|
+
|
12
|
+
namespace :group_notifications do
|
13
|
+
|
14
|
+
desc "Deliver all unsent APN Group notifications."
|
15
|
+
task :deliver => [:environment] do
|
16
|
+
APN::App.send_group_notifications
|
17
|
+
end
|
18
|
+
|
19
|
+
end # group_notifications
|
20
|
+
|
21
|
+
namespace :feedback do
|
22
|
+
|
23
|
+
desc "Process all devices that have feedback from APN."
|
24
|
+
task :process => [:environment] do
|
25
|
+
APN::App.process_devices
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end # apn
|
@@ -0,0 +1,19 @@
|
|
1
|
+
namespace :apn do
|
2
|
+
|
3
|
+
namespace :db do
|
4
|
+
|
5
|
+
task :migrate do
|
6
|
+
puts %{
|
7
|
+
This task no longer exists. Please generate the migrations like this:
|
8
|
+
|
9
|
+
$ ruby script/generate apn_migrations
|
10
|
+
|
11
|
+
Then just run the migrations like you would normally:
|
12
|
+
|
13
|
+
$ rake db:migrate
|
14
|
+
}.strip
|
15
|
+
end
|
16
|
+
|
17
|
+
end # db
|
18
|
+
|
19
|
+
end # apn
|