helios_aim 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +173 -0
- data/LICENSE +19 -0
- data/README.md +363 -0
- data/Rakefile +10 -0
- data/bin/helios +20 -0
- data/helios_aim.gemspec +47 -0
- data/lib/helios.rb +27 -0
- data/lib/helios/backend.rb +64 -0
- data/lib/helios/backend/data.rb +40 -0
- data/lib/helios/backend/in-app-purchase.rb +43 -0
- data/lib/helios/backend/newsstand.rb +97 -0
- data/lib/helios/backend/passbook.rb +41 -0
- data/lib/helios/backend/push-notification.rb +110 -0
- data/lib/helios/commands.rb +4 -0
- data/lib/helios/commands/console.rb +21 -0
- data/lib/helios/commands/link.rb +19 -0
- data/lib/helios/commands/new.rb +76 -0
- data/lib/helios/commands/server.rb +52 -0
- data/lib/helios/frontend.rb +66 -0
- data/lib/helios/frontend/fonts/icons.eot +0 -0
- data/lib/helios/frontend/fonts/icons.ttf +0 -0
- data/lib/helios/frontend/fonts/icons.woff +0 -0
- data/lib/helios/frontend/images/bg.jpg +0 -0
- data/lib/helios/frontend/images/helios.svg +33 -0
- data/lib/helios/frontend/javascripts/helios.coffee +72 -0
- data/lib/helios/frontend/javascripts/helios/collections.coffee +99 -0
- data/lib/helios/frontend/javascripts/helios/models.coffee +23 -0
- data/lib/helios/frontend/javascripts/helios/router.coffee +50 -0
- data/lib/helios/frontend/javascripts/helios/views.coffee +307 -0
- data/lib/helios/frontend/javascripts/vendor/backbone.datagrid.js +662 -0
- data/lib/helios/frontend/javascripts/vendor/backbone.js +1487 -0
- data/lib/helios/frontend/javascripts/vendor/backbone.paginator.js +1046 -0
- data/lib/helios/frontend/javascripts/vendor/codemirror.javascript.js +411 -0
- data/lib/helios/frontend/javascripts/vendor/codemirror.js +3047 -0
- data/lib/helios/frontend/javascripts/vendor/date.js +104 -0
- data/lib/helios/frontend/javascripts/vendor/foundation.js +331 -0
- data/lib/helios/frontend/javascripts/vendor/foundation/foundation.alerts.js +50 -0
- data/lib/helios/frontend/javascripts/vendor/foundation/foundation.clearing.js +478 -0
- data/lib/helios/frontend/javascripts/vendor/foundation/foundation.cookie.js +74 -0
- data/lib/helios/frontend/javascripts/vendor/foundation/foundation.dropdown.js +122 -0
- data/lib/helios/frontend/javascripts/vendor/foundation/foundation.forms.js +403 -0
- data/lib/helios/frontend/javascripts/vendor/foundation/foundation.joyride.js +613 -0
- data/lib/helios/frontend/javascripts/vendor/foundation/foundation.magellan.js +130 -0
- data/lib/helios/frontend/javascripts/vendor/foundation/foundation.orbit.js +355 -0
- data/lib/helios/frontend/javascripts/vendor/foundation/foundation.placeholder.js +159 -0
- data/lib/helios/frontend/javascripts/vendor/foundation/foundation.reveal.js +272 -0
- data/lib/helios/frontend/javascripts/vendor/foundation/foundation.section.js +183 -0
- data/lib/helios/frontend/javascripts/vendor/foundation/foundation.tooltips.js +195 -0
- data/lib/helios/frontend/javascripts/vendor/foundation/foundation.topbar.js +208 -0
- data/lib/helios/frontend/javascripts/vendor/jquery.js +9597 -0
- data/lib/helios/frontend/javascripts/vendor/jquery/jquery.fileupload-ui.js +807 -0
- data/lib/helios/frontend/javascripts/vendor/jquery/jquery.fileupload.js +1201 -0
- data/lib/helios/frontend/javascripts/vendor/jquery/jquery.ui.widget.js +530 -0
- data/lib/helios/frontend/javascripts/vendor/linkheaders.js +117 -0
- data/lib/helios/frontend/javascripts/vendor/underscore.js +1227 -0
- data/lib/helios/frontend/stylesheets/_codemirror.sass +219 -0
- data/lib/helios/frontend/stylesheets/_fonts.sass +80 -0
- data/lib/helios/frontend/stylesheets/_iphone.sass +141 -0
- data/lib/helios/frontend/stylesheets/_settings.scss +989 -0
- data/lib/helios/frontend/stylesheets/screen.sass +187 -0
- data/lib/helios/frontend/templates/data/entities.jst.tpl +11 -0
- data/lib/helios/frontend/templates/in-app-purchase/receipts.jst.tpl +11 -0
- data/lib/helios/frontend/templates/navigation.jst.tpl +31 -0
- data/lib/helios/frontend/templates/newsstand/issues.jst.tpl +16 -0
- data/lib/helios/frontend/templates/newsstand/new.jst.tpl +28 -0
- data/lib/helios/frontend/templates/passbook/passes.jst.tpl +11 -0
- data/lib/helios/frontend/templates/push-notification/compose.jst.tpl +70 -0
- data/lib/helios/frontend/templates/push-notification/devices.jst.tpl +17 -0
- data/lib/helios/frontend/views/index.haml +22 -0
- data/lib/helios/templates/.env.erb +1 -0
- data/lib/helios/templates/.gitignore +3 -0
- data/lib/helios/templates/Gemfile.erb +10 -0
- data/lib/helios/templates/Procfile.erb +1 -0
- data/lib/helios/templates/README.md.erb +4 -0
- data/lib/helios/templates/config.ru.erb +11 -0
- data/lib/helios/version.rb +3 -0
- metadata +475 -0
data/Rakefile
ADDED
data/bin/helios
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'commander/import'
|
4
|
+
$:.push File.expand_path("../../lib", __FILE__)
|
5
|
+
|
6
|
+
require 'helios/version'
|
7
|
+
|
8
|
+
HighLine.track_eof = false # Fix for built-in Ruby
|
9
|
+
Signal.trap("INT") {} # Suppress backtrace when exiting command
|
10
|
+
|
11
|
+
program :version, Helios::VERSION
|
12
|
+
program :description, 'A command-line interface for building mobile infrastructures'
|
13
|
+
|
14
|
+
program :help, 'Author', 'Mattt Thompson <m@mattt.me>'
|
15
|
+
program :help, 'Website', 'https://helios.io'
|
16
|
+
program :help_formatter, :compact
|
17
|
+
|
18
|
+
default_command :help
|
19
|
+
|
20
|
+
require 'helios/commands'
|
data/helios_aim.gemspec
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "helios/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "helios_aim"
|
7
|
+
s.authors = ["Mattt Thompson"]
|
8
|
+
s.email = "mattt@heroku.com"
|
9
|
+
s.license = "MIT"
|
10
|
+
s.homepage = "http://helios.io"
|
11
|
+
s.version = Helios::VERSION
|
12
|
+
s.platform = Gem::Platform::RUBY
|
13
|
+
s.summary = "An extensible open-source mobile backend framework"
|
14
|
+
s.description = "Helios is an open-source framework that provides essential backend services for iOS apps, from data synchronization and user accounts to push notifications, in-app purchases, and passbook integration. It allows developers to get a client-server app up-and-running in just a few minutes, and seamlessly incorporate functionality as necessary."
|
15
|
+
|
16
|
+
s.add_dependency "commander", "~> 4.1"
|
17
|
+
s.add_dependency "foreman", "~> 0.63"
|
18
|
+
s.add_dependency "rack-contrib", "~> 1.1"
|
19
|
+
s.add_dependency "rack-push-notification", "~> 0.4"
|
20
|
+
s.add_dependency "rack-in-app-purchase", "~> 0.1"
|
21
|
+
s.add_dependency "rack-passbook", "~> 0.1"
|
22
|
+
s.add_dependency "rack-newsstand", "~> 0.1"
|
23
|
+
s.add_dependency "rack-scaffold_aim"
|
24
|
+
s.add_dependency "core_data"
|
25
|
+
s.add_dependency "json", "~> 1.7"
|
26
|
+
s.add_dependency "coffee-script", "~> 2.2"
|
27
|
+
s.add_dependency "sinatra", "~> 1.3"
|
28
|
+
s.add_dependency "sinatra-contrib", "~> 1.3"
|
29
|
+
s.add_dependency "sinatra-assetpack", "~> 0.2.2"
|
30
|
+
s.add_dependency "sinatra-backbone", "~> 0.1.1"
|
31
|
+
s.add_dependency "sinatra-param", "~> 0.1"
|
32
|
+
s.add_dependency "sinatra-support", "~> 1.2"
|
33
|
+
s.add_dependency "haml", ">= 3.1"
|
34
|
+
s.add_dependency "compass", "~> 0.12"
|
35
|
+
s.add_dependency "zurb-foundation", "4.1.2"
|
36
|
+
s.add_dependency "rails-database-url", "~> 1.0"
|
37
|
+
s.add_dependency "fog", "~> 1.10"
|
38
|
+
s.add_dependency "houston", "~> 0.2"
|
39
|
+
|
40
|
+
s.add_development_dependency "rake"
|
41
|
+
s.add_development_dependency "rspec"
|
42
|
+
|
43
|
+
s.files = Dir["./**/*"].reject{|file| file =~ /\.\/(bin|example|log|pkg|script|spec|test|vendor)/} + Dir.glob("./lib/helios/templates/*", File::FNM_DOTMATCH)
|
44
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
45
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
46
|
+
s.require_paths = ["lib"]
|
47
|
+
end
|
data/lib/helios.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'rack'
|
2
|
+
|
3
|
+
module Helios
|
4
|
+
class Application
|
5
|
+
def initialize(app = nil, options = {}, &block)
|
6
|
+
@app = Rack::Builder.new do
|
7
|
+
map '/admin' do
|
8
|
+
use Rack::Auth::Basic, "Restricted Area" do |username, password|
|
9
|
+
username == (ENV['HELIOS_ADMIN_USERNAME'] || "") and password == (ENV['HELIOS_ADMIN_PASSWORD'] || "")
|
10
|
+
end if ENV['HELIOS_ADMIN_USERNAME'] or ENV['HELIOS_ADMIN_PASSWORD']
|
11
|
+
|
12
|
+
run Helios::Frontend.new
|
13
|
+
end
|
14
|
+
|
15
|
+
run Helios::Backend.new(&block)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def call(env)
|
20
|
+
@app.call(env)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
require 'helios/backend'
|
26
|
+
require 'helios/frontend'
|
27
|
+
require 'helios/version'
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'rack'
|
2
|
+
|
3
|
+
module Helios
|
4
|
+
class Backend < Rack::Builder
|
5
|
+
DEFAULT_PATHS = {
|
6
|
+
data: '/'
|
7
|
+
}
|
8
|
+
|
9
|
+
require 'rails-database-url' if const_defined?(:Rails)
|
10
|
+
|
11
|
+
def initialize(*args, &block)
|
12
|
+
raise ArgumentError, "Missing block" unless block_given?
|
13
|
+
super(&nil)
|
14
|
+
|
15
|
+
@services = {}
|
16
|
+
|
17
|
+
instance_eval(&block)
|
18
|
+
end
|
19
|
+
|
20
|
+
def call(env)
|
21
|
+
return super(env) unless env["REQUEST_METHOD"] == "OPTIONS" and env["REQUEST_PATH"] == "/"
|
22
|
+
|
23
|
+
links = []
|
24
|
+
@services.each do |path, middleware|
|
25
|
+
links << %{<#{path}>; rel="#{middleware}"}
|
26
|
+
end
|
27
|
+
|
28
|
+
[206, {"Link" => links.join("\n")}, []]
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def service(identifier, options = {}, &block)
|
34
|
+
if identifier.is_a?(Class)
|
35
|
+
middleware = identifier
|
36
|
+
else
|
37
|
+
begin
|
38
|
+
middleware = Helios::Backend.const_get(constantize(identifier))
|
39
|
+
rescue NameError
|
40
|
+
raise LoadError, "Could not find matching service for #{identifier.inspect} (Helios::Backend::#{constantize(identifier)}). You may need to install an additional gem (such as helios-#{identifier})."
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
path = "/#{(options.delete(:root) || DEFAULT_PATHS[identifier] || identifier)}".squeeze("/")
|
45
|
+
|
46
|
+
map path do
|
47
|
+
instance_eval(&block) if block_given?
|
48
|
+
run middleware.new(self, options)
|
49
|
+
end
|
50
|
+
|
51
|
+
@services[path] = middleware
|
52
|
+
end
|
53
|
+
|
54
|
+
def constantize(identifier)
|
55
|
+
identifier.to_s.split(/([[:alpha:]]*)/).select{|c| /[[:alpha:]]/ === c}.map(&:capitalize).join("")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
require 'helios/backend/data'
|
61
|
+
require 'helios/backend/in-app-purchase'
|
62
|
+
require 'helios/backend/passbook'
|
63
|
+
require 'helios/backend/push-notification'
|
64
|
+
require 'helios/backend/newsstand'
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'core_data'
|
2
|
+
require 'sequel'
|
3
|
+
|
4
|
+
require 'rack/scaffold_aim'
|
5
|
+
|
6
|
+
require 'sinatra/base'
|
7
|
+
require 'sinatra/param'
|
8
|
+
|
9
|
+
|
10
|
+
class Helios::Backend::Data < Sinatra::Base
|
11
|
+
helpers Sinatra::Param
|
12
|
+
|
13
|
+
def initialize(app, options = {})
|
14
|
+
super(Rack::Scaffold.new(options))
|
15
|
+
|
16
|
+
@model = CoreData::DataModel.new(options[:model]) rescue nil
|
17
|
+
end
|
18
|
+
|
19
|
+
before do
|
20
|
+
content_type :json
|
21
|
+
end
|
22
|
+
|
23
|
+
options '/resources' do
|
24
|
+
|
25
|
+
links = []
|
26
|
+
@model.entities.each do |entity|
|
27
|
+
links << %{</#{entity.name.downcase.pluralize}>; rel="resource"}
|
28
|
+
end
|
29
|
+
|
30
|
+
response['Link'] = links.join("\n")
|
31
|
+
|
32
|
+
@model.entities.collect{ |entity|
|
33
|
+
{
|
34
|
+
name: entity.name,
|
35
|
+
url: "/#{entity.name.downcase.pluralize}",
|
36
|
+
attributes: Hash[entity.attributes.collect{|attribute| [attribute.name, attribute.type]}]
|
37
|
+
}
|
38
|
+
}.to_json
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'rack/in-app-purchase'
|
2
|
+
|
3
|
+
require 'sinatra/base'
|
4
|
+
require 'sinatra/param'
|
5
|
+
|
6
|
+
class Helios::Backend::InAppPurchase < Sinatra::Base
|
7
|
+
helpers Sinatra::Param
|
8
|
+
|
9
|
+
def initialize(app, options = {}, &block)
|
10
|
+
super(Rack::InAppPurchase.new)
|
11
|
+
end
|
12
|
+
|
13
|
+
before do
|
14
|
+
content_type :json
|
15
|
+
end
|
16
|
+
|
17
|
+
helpers Sinatra::Param
|
18
|
+
|
19
|
+
get '/receipts' do
|
20
|
+
param :q, String
|
21
|
+
|
22
|
+
receipts = Rack::InAppPurchase::Receipt.dataset
|
23
|
+
receipts = receipts.filter("tsv @@ to_tsquery('english', ?)", "#{params[:q]}:*") if params[:q] and not params[:q].empty?
|
24
|
+
|
25
|
+
if params[:page] or params[:per_page]
|
26
|
+
param :page, Integer, default: 1, min: 1
|
27
|
+
param :per_page, Integer, default: 100, in: (1..100)
|
28
|
+
|
29
|
+
{
|
30
|
+
receipts: receipts.limit(params[:per_page], (params[:page] - 1) * params[:per_page]).naked.all,
|
31
|
+
page: params[:page],
|
32
|
+
total: receipts.count
|
33
|
+
}.to_json
|
34
|
+
else
|
35
|
+
param :limit, Integer, default: 100, in: (1..100)
|
36
|
+
param :offset, Integer, default: 0, min: 0
|
37
|
+
|
38
|
+
{
|
39
|
+
receipts: receipts.limit(params[:limit], params[:offset]).naked.all
|
40
|
+
}.to_json
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'rack/newsstand'
|
2
|
+
|
3
|
+
require 'sinatra/base'
|
4
|
+
require 'sinatra/param'
|
5
|
+
|
6
|
+
require 'fog'
|
7
|
+
|
8
|
+
class Helios::Backend::Newsstand < Sinatra::Base
|
9
|
+
helpers Sinatra::Param
|
10
|
+
|
11
|
+
def initialize(app, options = {}, &block)
|
12
|
+
super(Rack::Newsstand.new)
|
13
|
+
|
14
|
+
@storage = Fog::Storage.new(options[:storage]) if options[:storage]
|
15
|
+
end
|
16
|
+
|
17
|
+
before do
|
18
|
+
content_type :json
|
19
|
+
end
|
20
|
+
|
21
|
+
get '/issues/?' do
|
22
|
+
pass unless request.accept? 'application/json'
|
23
|
+
|
24
|
+
param :q, String
|
25
|
+
|
26
|
+
issues = Rack::Newsstand::Issue.dataset
|
27
|
+
issues = issues.filter("tsv @@ to_tsquery('english', ?)", "#{params[:q]}:*") if params[:q] and not params[:q].empty?
|
28
|
+
|
29
|
+
if params[:page] or params[:per_page]
|
30
|
+
param :page, Integer, default: 1, min: 1
|
31
|
+
param :per_page, Integer, default: 100, in: (1..100)
|
32
|
+
|
33
|
+
{
|
34
|
+
issues: issues.limit(params[:per_page], (params[:page] - 1) * params[:per_page]).naked.all,
|
35
|
+
page: params[:page],
|
36
|
+
total: issues.count
|
37
|
+
}.to_json
|
38
|
+
else
|
39
|
+
param :limit, Integer, default: 100, in: (1..100)
|
40
|
+
param :offset, Integer, default: 0, min: 0
|
41
|
+
|
42
|
+
{
|
43
|
+
issues: issues.limit(params[:limit], params[:offset]).naked.all
|
44
|
+
}.to_json
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
head '/issues/new?' do
|
49
|
+
status 503 and return unless @storage
|
50
|
+
|
51
|
+
status 204
|
52
|
+
end
|
53
|
+
|
54
|
+
post '/issues/?' do
|
55
|
+
status 503 and return unless @storage
|
56
|
+
|
57
|
+
param :name, String, empty: false
|
58
|
+
param :summary, String
|
59
|
+
|
60
|
+
issue = Rack::Newsstand::Issue.new(params)
|
61
|
+
|
62
|
+
if issue.valid?
|
63
|
+
directory = @storage.directories.create(key: "newsstand-issue-#{issue.name}-#{Time.now.to_i}", public: true)
|
64
|
+
|
65
|
+
covers, assets = {}, []
|
66
|
+
[:covers, :assets].each do |attribute|
|
67
|
+
(params[attribute] || []).each do |f|
|
68
|
+
file = directory.files.create(
|
69
|
+
key: File.basename(f[:filename]),
|
70
|
+
body: File.open(f[:tempfile]),
|
71
|
+
public: true
|
72
|
+
)
|
73
|
+
|
74
|
+
case attribute
|
75
|
+
when :covers
|
76
|
+
covers["SOURCE"] = file.public_url
|
77
|
+
when :assets
|
78
|
+
assets << file.public_url
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
issue.set(cover_urls: covers, asset_urls: assets)
|
84
|
+
|
85
|
+
if issue.save
|
86
|
+
status 201
|
87
|
+
issue.to_json
|
88
|
+
else
|
89
|
+
status 400
|
90
|
+
{errors: issue.errors}.to_json
|
91
|
+
end
|
92
|
+
else
|
93
|
+
status 400
|
94
|
+
{errors: issue.errors}.to_json
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'rack/passbook'
|
2
|
+
|
3
|
+
require 'sinatra/base'
|
4
|
+
require 'sinatra/param'
|
5
|
+
|
6
|
+
class Helios::Backend::Passbook < Sinatra::Base
|
7
|
+
helpers Sinatra::Param
|
8
|
+
|
9
|
+
def initialize(app, options = {}, &block)
|
10
|
+
super(Rack::Passbook.new)
|
11
|
+
end
|
12
|
+
|
13
|
+
before do
|
14
|
+
content_type :json
|
15
|
+
end
|
16
|
+
|
17
|
+
get '/passes' do
|
18
|
+
param :q, String
|
19
|
+
|
20
|
+
passes = Rack::Passbook::Pass.dataset
|
21
|
+
passes = passes.filter("tsv @@ to_tsquery('english', ?)", "#{params[:q]}:*") if params[:q] and not params[:q].empty?
|
22
|
+
|
23
|
+
if params[:page] or params[:per_page]
|
24
|
+
param :page, Integer, default: 1, min: 1
|
25
|
+
param :per_page, Integer, default: 100, in: (1..100)
|
26
|
+
|
27
|
+
{
|
28
|
+
passes: passes.limit(params[:per_page], (params[:page] - 1) * params[:per_page]).naked.all,
|
29
|
+
page: params[:page],
|
30
|
+
total: passes.count
|
31
|
+
}.to_json
|
32
|
+
else
|
33
|
+
param :limit, Integer, default: 100, in: (1..100)
|
34
|
+
param :offset, Integer, default: 0, min: 0
|
35
|
+
|
36
|
+
{
|
37
|
+
passes: passes.limit(params[:limit], params[:offset]).naked.all
|
38
|
+
}.to_json
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'rack/push-notification'
|
2
|
+
|
3
|
+
require 'sinatra/base'
|
4
|
+
require 'sinatra/param'
|
5
|
+
|
6
|
+
require 'houston'
|
7
|
+
|
8
|
+
class Helios::Backend::PushNotification < Sinatra::Base
|
9
|
+
helpers Sinatra::Param
|
10
|
+
attr_reader :apn_certificate, :apn_environment
|
11
|
+
|
12
|
+
def initialize(app, options = {}, &block)
|
13
|
+
super(Rack::PushNotification.new)
|
14
|
+
|
15
|
+
@apn_certificate = options[:apn_certificate] || ENV['APN_CERTIFICATE']
|
16
|
+
@apn_environment = options[:apn_environment] || ENV['APN_ENVIRONMENT']
|
17
|
+
end
|
18
|
+
|
19
|
+
before do
|
20
|
+
content_type :json
|
21
|
+
end
|
22
|
+
|
23
|
+
get '/devices/?' do
|
24
|
+
param :q, String
|
25
|
+
|
26
|
+
devices = ::Rack::PushNotification::Device.dataset
|
27
|
+
devices = devices.filter("tsv @@ to_tsquery('english', ?)", "#{params[:q]}:*") if params[:q] and not params[:q].empty?
|
28
|
+
|
29
|
+
if params[:page] or params[:per_page]
|
30
|
+
param :page, Integer, default: 1, min: 1
|
31
|
+
param :per_page, Integer, default: 100, in: (1..100)
|
32
|
+
|
33
|
+
{
|
34
|
+
devices: devices.limit(params[:per_page], (params[:page] - 1) * params[:per_page]),
|
35
|
+
page: params[:page],
|
36
|
+
total: devices.count
|
37
|
+
}.to_json
|
38
|
+
else
|
39
|
+
param :limit, Integer, default: 100, in: (1..100)
|
40
|
+
param :offset, Integer, default: 0, min: 0
|
41
|
+
|
42
|
+
{
|
43
|
+
devices: devices.limit(params[:limit], params[:offset])
|
44
|
+
}.to_json
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
get '/devices/:token/?' do
|
49
|
+
record = ::Rack::PushNotification::Device.find(token: params[:token])
|
50
|
+
|
51
|
+
if record
|
52
|
+
{device: record}.to_json
|
53
|
+
else
|
54
|
+
status 404
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
head '/message' do
|
59
|
+
status 503 and return unless client
|
60
|
+
|
61
|
+
status 204
|
62
|
+
end
|
63
|
+
|
64
|
+
post '/message' do
|
65
|
+
status 503 and return unless client
|
66
|
+
|
67
|
+
param :payload, String, empty: false
|
68
|
+
param :tokens, Array, empty: false
|
69
|
+
|
70
|
+
tokens = params[:tokens] || ::Rack::PushNotification::Device.all.collect(&:token)
|
71
|
+
|
72
|
+
options = JSON.parse(params[:payload])
|
73
|
+
options[:alert] = options["aps"]["alert"]
|
74
|
+
options[:badge] = options["aps"]["badge"]
|
75
|
+
options[:sound] = options["aps"]["sound"]
|
76
|
+
options.delete("aps")
|
77
|
+
|
78
|
+
begin
|
79
|
+
notifications = tokens.collect{|token| Houston::Notification.new(options.update({device: token}))}
|
80
|
+
client.push(*notifications)
|
81
|
+
|
82
|
+
status 204
|
83
|
+
rescue => error
|
84
|
+
status 500
|
85
|
+
|
86
|
+
{error: error}.to_json
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def client
|
93
|
+
begin
|
94
|
+
return nil unless apn_certificate and ::File.exist?(apn_certificate)
|
95
|
+
|
96
|
+
client = case apn_environment.to_sym
|
97
|
+
when :development
|
98
|
+
Houston::Client.development
|
99
|
+
when :production
|
100
|
+
Houston::Client.production
|
101
|
+
end
|
102
|
+
|
103
|
+
client.certificate = ::File.read(apn_certificate)
|
104
|
+
|
105
|
+
return client
|
106
|
+
rescue
|
107
|
+
return nil
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|