helios_aim 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +15 -0
  2. data/Gemfile +3 -0
  3. data/Gemfile.lock +173 -0
  4. data/LICENSE +19 -0
  5. data/README.md +363 -0
  6. data/Rakefile +10 -0
  7. data/bin/helios +20 -0
  8. data/helios_aim.gemspec +47 -0
  9. data/lib/helios.rb +27 -0
  10. data/lib/helios/backend.rb +64 -0
  11. data/lib/helios/backend/data.rb +40 -0
  12. data/lib/helios/backend/in-app-purchase.rb +43 -0
  13. data/lib/helios/backend/newsstand.rb +97 -0
  14. data/lib/helios/backend/passbook.rb +41 -0
  15. data/lib/helios/backend/push-notification.rb +110 -0
  16. data/lib/helios/commands.rb +4 -0
  17. data/lib/helios/commands/console.rb +21 -0
  18. data/lib/helios/commands/link.rb +19 -0
  19. data/lib/helios/commands/new.rb +76 -0
  20. data/lib/helios/commands/server.rb +52 -0
  21. data/lib/helios/frontend.rb +66 -0
  22. data/lib/helios/frontend/fonts/icons.eot +0 -0
  23. data/lib/helios/frontend/fonts/icons.ttf +0 -0
  24. data/lib/helios/frontend/fonts/icons.woff +0 -0
  25. data/lib/helios/frontend/images/bg.jpg +0 -0
  26. data/lib/helios/frontend/images/helios.svg +33 -0
  27. data/lib/helios/frontend/javascripts/helios.coffee +72 -0
  28. data/lib/helios/frontend/javascripts/helios/collections.coffee +99 -0
  29. data/lib/helios/frontend/javascripts/helios/models.coffee +23 -0
  30. data/lib/helios/frontend/javascripts/helios/router.coffee +50 -0
  31. data/lib/helios/frontend/javascripts/helios/views.coffee +307 -0
  32. data/lib/helios/frontend/javascripts/vendor/backbone.datagrid.js +662 -0
  33. data/lib/helios/frontend/javascripts/vendor/backbone.js +1487 -0
  34. data/lib/helios/frontend/javascripts/vendor/backbone.paginator.js +1046 -0
  35. data/lib/helios/frontend/javascripts/vendor/codemirror.javascript.js +411 -0
  36. data/lib/helios/frontend/javascripts/vendor/codemirror.js +3047 -0
  37. data/lib/helios/frontend/javascripts/vendor/date.js +104 -0
  38. data/lib/helios/frontend/javascripts/vendor/foundation.js +331 -0
  39. data/lib/helios/frontend/javascripts/vendor/foundation/foundation.alerts.js +50 -0
  40. data/lib/helios/frontend/javascripts/vendor/foundation/foundation.clearing.js +478 -0
  41. data/lib/helios/frontend/javascripts/vendor/foundation/foundation.cookie.js +74 -0
  42. data/lib/helios/frontend/javascripts/vendor/foundation/foundation.dropdown.js +122 -0
  43. data/lib/helios/frontend/javascripts/vendor/foundation/foundation.forms.js +403 -0
  44. data/lib/helios/frontend/javascripts/vendor/foundation/foundation.joyride.js +613 -0
  45. data/lib/helios/frontend/javascripts/vendor/foundation/foundation.magellan.js +130 -0
  46. data/lib/helios/frontend/javascripts/vendor/foundation/foundation.orbit.js +355 -0
  47. data/lib/helios/frontend/javascripts/vendor/foundation/foundation.placeholder.js +159 -0
  48. data/lib/helios/frontend/javascripts/vendor/foundation/foundation.reveal.js +272 -0
  49. data/lib/helios/frontend/javascripts/vendor/foundation/foundation.section.js +183 -0
  50. data/lib/helios/frontend/javascripts/vendor/foundation/foundation.tooltips.js +195 -0
  51. data/lib/helios/frontend/javascripts/vendor/foundation/foundation.topbar.js +208 -0
  52. data/lib/helios/frontend/javascripts/vendor/jquery.js +9597 -0
  53. data/lib/helios/frontend/javascripts/vendor/jquery/jquery.fileupload-ui.js +807 -0
  54. data/lib/helios/frontend/javascripts/vendor/jquery/jquery.fileupload.js +1201 -0
  55. data/lib/helios/frontend/javascripts/vendor/jquery/jquery.ui.widget.js +530 -0
  56. data/lib/helios/frontend/javascripts/vendor/linkheaders.js +117 -0
  57. data/lib/helios/frontend/javascripts/vendor/underscore.js +1227 -0
  58. data/lib/helios/frontend/stylesheets/_codemirror.sass +219 -0
  59. data/lib/helios/frontend/stylesheets/_fonts.sass +80 -0
  60. data/lib/helios/frontend/stylesheets/_iphone.sass +141 -0
  61. data/lib/helios/frontend/stylesheets/_settings.scss +989 -0
  62. data/lib/helios/frontend/stylesheets/screen.sass +187 -0
  63. data/lib/helios/frontend/templates/data/entities.jst.tpl +11 -0
  64. data/lib/helios/frontend/templates/in-app-purchase/receipts.jst.tpl +11 -0
  65. data/lib/helios/frontend/templates/navigation.jst.tpl +31 -0
  66. data/lib/helios/frontend/templates/newsstand/issues.jst.tpl +16 -0
  67. data/lib/helios/frontend/templates/newsstand/new.jst.tpl +28 -0
  68. data/lib/helios/frontend/templates/passbook/passes.jst.tpl +11 -0
  69. data/lib/helios/frontend/templates/push-notification/compose.jst.tpl +70 -0
  70. data/lib/helios/frontend/templates/push-notification/devices.jst.tpl +17 -0
  71. data/lib/helios/frontend/views/index.haml +22 -0
  72. data/lib/helios/templates/.env.erb +1 -0
  73. data/lib/helios/templates/.gitignore +3 -0
  74. data/lib/helios/templates/Gemfile.erb +10 -0
  75. data/lib/helios/templates/Procfile.erb +1 -0
  76. data/lib/helios/templates/README.md.erb +4 -0
  77. data/lib/helios/templates/config.ru.erb +11 -0
  78. data/lib/helios/version.rb +3 -0
  79. metadata +475 -0
@@ -0,0 +1,10 @@
1
+ require "bundler"
2
+ Bundler.setup
3
+
4
+ gemspec = eval(File.read("helios.gemspec"))
5
+
6
+ task :build => "#{gemspec.full_name}.gem"
7
+
8
+ file "#{gemspec.full_name}.gem" => gemspec.files + ["helios.gemspec"] do
9
+ system "gem build helios.gemspec"
10
+ end
@@ -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'
@@ -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
@@ -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