tent-status 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. data/.gitignore +21 -0
  2. data/.kick +8 -0
  3. data/Gemfile +12 -0
  4. data/Gemfile.lock +232 -0
  5. data/LICENSE.txt +22 -0
  6. data/Procfile +1 -0
  7. data/README.md +14 -0
  8. data/Rakefile +43 -0
  9. data/assets/images/.gitkeep +0 -0
  10. data/assets/images/chosen-sprite.png +0 -0
  11. data/assets/images/glyphicons-halflings-white.png +0 -0
  12. data/assets/images/glyphicons-halflings.png +0 -0
  13. data/assets/javascripts/.gitkeep +0 -0
  14. data/assets/javascripts/application.js.coffee +122 -0
  15. data/assets/javascripts/backbone.js +1443 -0
  16. data/assets/javascripts/backbone_sync.js.coffee +60 -0
  17. data/assets/javascripts/boot.js.coffee +3 -0
  18. data/assets/javascripts/chosen.jquery.js +1129 -0
  19. data/assets/javascripts/collections/.gitkeep +0 -0
  20. data/assets/javascripts/collections/followers.js.coffee +5 -0
  21. data/assets/javascripts/collections/followings.js.coffee +5 -0
  22. data/assets/javascripts/collections/groups.js.coffee +5 -0
  23. data/assets/javascripts/collections/posts.js.coffee +5 -0
  24. data/assets/javascripts/fetch_pool.js.coffee +25 -0
  25. data/assets/javascripts/helpers/.gitkeep +0 -0
  26. data/assets/javascripts/helpers/formatting.js.coffee +17 -0
  27. data/assets/javascripts/hogan.js +706 -0
  28. data/assets/javascripts/http.js.coffee +18 -0
  29. data/assets/javascripts/jquery.js +9301 -0
  30. data/assets/javascripts/models/.gitkeep +0 -0
  31. data/assets/javascripts/models/follower.js.coffee +27 -0
  32. data/assets/javascripts/models/following.js.coffee +27 -0
  33. data/assets/javascripts/models/group.js.coffee +4 -0
  34. data/assets/javascripts/models/post.js.coffee +32 -0
  35. data/assets/javascripts/models/profile.js.coffee +30 -0
  36. data/assets/javascripts/moment.js +1106 -0
  37. data/assets/javascripts/paginator.js.coffee +67 -0
  38. data/assets/javascripts/router.js.coffee +71 -0
  39. data/assets/javascripts/routers/.gitkeep +0 -0
  40. data/assets/javascripts/routers/followers.js.coffee +14 -0
  41. data/assets/javascripts/routers/followings.js.coffee +14 -0
  42. data/assets/javascripts/routers/posts.js.coffee +98 -0
  43. data/assets/javascripts/templates/.gitkeep +0 -0
  44. data/assets/javascripts/templates/_follower.js.mustache.slim +17 -0
  45. data/assets/javascripts/templates/_following.js.mustache.slim +17 -0
  46. data/assets/javascripts/templates/_new_post_form.js.mustache.slim +10 -0
  47. data/assets/javascripts/templates/_post.js.mustache.slim +10 -0
  48. data/assets/javascripts/templates/_post_inner.js.mustache.slim +71 -0
  49. data/assets/javascripts/templates/_profile_stats.js.mustache.slim +11 -0
  50. data/assets/javascripts/templates/_reply_form.js.mustache.slim +13 -0
  51. data/assets/javascripts/templates/conversation.js.mustache.slim +13 -0
  52. data/assets/javascripts/templates/followers.js.mustache.slim +19 -0
  53. data/assets/javascripts/templates/followings.js.mustache.slim +22 -0
  54. data/assets/javascripts/templates/posts.js.mustache.slim +25 -0
  55. data/assets/javascripts/templates/profile.js.mustache.slim +39 -0
  56. data/assets/javascripts/underscore.js +1059 -0
  57. data/assets/javascripts/view.js.coffee +140 -0
  58. data/assets/javascripts/views/.gitkeep +0 -0
  59. data/assets/javascripts/views/container.js.coffee +6 -0
  60. data/assets/javascripts/views/expanding_textarea.js.coffee +14 -0
  61. data/assets/javascripts/views/fetch_posts_pool.js.coffee +61 -0
  62. data/assets/javascripts/views/follower_groups_form.js.coffee +26 -0
  63. data/assets/javascripts/views/followers.js.coffee +28 -0
  64. data/assets/javascripts/views/following_groups_form.js.coffee +25 -0
  65. data/assets/javascripts/views/followings.js.coffee +27 -0
  66. data/assets/javascripts/views/new_following_form.js.coffee +15 -0
  67. data/assets/javascripts/views/new_post_form.js.coffee +178 -0
  68. data/assets/javascripts/views/post.js.coffee +139 -0
  69. data/assets/javascripts/views/posts.js.coffee +55 -0
  70. data/assets/javascripts/views/posts/conversation.js.coffee +18 -0
  71. data/assets/javascripts/views/profile.js.coffee +29 -0
  72. data/assets/javascripts/views/profile_follow_button.js.coffee +29 -0
  73. data/assets/javascripts/views/profile_stats.js.coffee +38 -0
  74. data/assets/javascripts/views/remove_follower_btn.js.coffee +18 -0
  75. data/assets/javascripts/views/reply_post_form.js.coffee +30 -0
  76. data/assets/javascripts/views/unfollow_btn.js.coffee +18 -0
  77. data/assets/stylesheets/.gitkeep +0 -0
  78. data/assets/stylesheets/application.css.sass +117 -0
  79. data/assets/stylesheets/bootstrap-responsive.css +1040 -0
  80. data/assets/stylesheets/bootstrap.css.erb +5624 -0
  81. data/assets/stylesheets/chosen.css.erb +397 -0
  82. data/config.ru +14 -0
  83. data/config/asset_sync.rb +12 -0
  84. data/config/evergreen.rb +16 -0
  85. data/lib/tent-status.rb +6 -0
  86. data/lib/tent-status/app.rb +263 -0
  87. data/lib/tent-status/models/user.rb +39 -0
  88. data/lib/tent-status/sprockets/environment.rb +25 -0
  89. data/lib/tent-status/sprockets/helpers.rb +5 -0
  90. data/lib/tent-status/views/application.slim +69 -0
  91. data/lib/tent-status/views/auth.slim +8 -0
  92. data/tent-status.gemspec +34 -0
  93. metadata +415 -0
@@ -0,0 +1,263 @@
1
+ require 'sinatra/base'
2
+ require 'data_mapper'
3
+ require 'sprockets'
4
+ require 'uglifier'
5
+ require 'tent-client'
6
+ require 'rack/csrf'
7
+ require 'hashie'
8
+ require 'uri'
9
+ require 'slim'
10
+ require 'hogan_assets'
11
+ require 'oj'
12
+
13
+ module Tent
14
+ class Status < Sinatra::Base
15
+ require 'tent-status/sprockets/environment'
16
+ require 'tent-status/models/user'
17
+
18
+ configure do
19
+ set :asset_manifest, Oj.load(File.read(ENV['STATUS_ASSET_MANIFEST'])) if ENV['STATUS_ASSET_MANIFEST']
20
+ set :cdn_url, ENV['STATUS_CDN_URL']
21
+ set :assets, SprocketsEnvironment.assets
22
+ end
23
+
24
+ use Rack::Csrf
25
+
26
+ helpers do
27
+ def path_prefix
28
+ env['SCRIPT_NAME']
29
+ end
30
+
31
+ def asset_path(path)
32
+ path = asset_manifest_path(path) || settings.assets.find_asset(path).digest_path
33
+ if settings.cdn_url?
34
+ "#{settings.cdn_url}/assets/#{path}"
35
+ else
36
+ full_path("/assets/#{path}")
37
+ end
38
+ end
39
+
40
+ def asset_manifest_path(asset)
41
+ if settings.respond_to?(:asset_manifest?) && settings.asset_manifest?
42
+ settings.asset_manifest['files'].detect { |k,v| v['logical_path'] == asset }[0]
43
+ end
44
+ end
45
+
46
+ def full_path(path)
47
+ "#{path_prefix}/#{path}".gsub(%r{//}, '/')
48
+ end
49
+
50
+ def tent_api_root
51
+ domain_entity + '/tent'
52
+ end
53
+
54
+ def full_url(path)
55
+ if guest_user
56
+ prefix = guest_user.entity
57
+ else
58
+ prefix = self_url_root
59
+ end
60
+ (prefix + full_path(path))
61
+ end
62
+
63
+ def self_url_root
64
+ env['rack.url_scheme'] + "://" + env['HTTP_HOST']
65
+ end
66
+
67
+ def client
68
+ env['tent.client']
69
+ end
70
+
71
+ def guest_client
72
+ env['tent.guest_client']
73
+ end
74
+
75
+ def csrf_tag
76
+ Rack::Csrf.tag(env)
77
+ end
78
+
79
+ def csrf_meta_tag(options = {})
80
+ Rack::Csrf.metatag(env, options)
81
+ end
82
+
83
+ def basic_profile
84
+ @basic_profile ||= (client.profile.get.body || {})['https://tent.io/types/info/basic/v0.1.0'] || {}
85
+ end
86
+
87
+ def current_user
88
+ return unless defined?(TentD)
89
+ current = TentD::Model::User.current
90
+ current if session[:current_user_id] == current.id
91
+ end
92
+
93
+ def guest_user
94
+ return unless defined?(TentD)
95
+ return unless session[:current_user_id]
96
+ user = TentD::Model::User.get(session[:current_user_id])
97
+ current = TentD::Model::User.current
98
+ return if session[:current_user_id] == current.id
99
+ user if user && (session[:current_user_id] == user.id)
100
+ end
101
+
102
+ def domain_entity
103
+ env['rack.url_scheme'] + '://' + env['HTTP_HOST']
104
+ end
105
+
106
+ def authenticate!
107
+ halt 403 unless current_user
108
+ end
109
+ end
110
+
111
+ def json(data)
112
+ [200, { 'Content-Type' => 'application/json' }, [data.to_json]]
113
+ end
114
+
115
+ if ENV['RACK_ENV'] != 'production'
116
+ get '/assets/*' do
117
+ new_env = env.clone
118
+ new_env["PATH_INFO"].gsub!("/assets", "")
119
+ settings.assets.call(new_env)
120
+ end
121
+ end
122
+
123
+ get '/' do
124
+ slim :application
125
+ end
126
+
127
+ get '/api/profile' do
128
+ res = (params.delete('guest') ? guest_client : client).profile.get
129
+ json res.body
130
+ end
131
+
132
+ get '/api/posts/count' do
133
+ res = (params.delete('guest') ? guest_client : client).post.count params.merge(
134
+ :post_types => ["https://tent.io/types/post/status/v0.1.0", "https://tent.io/types/post/repost/v0.1.0"].join(',')
135
+ )
136
+ json res.body
137
+ end
138
+
139
+ get '/api/posts' do
140
+ res = (params.delete('guest') ? guest_client : client).post.list params.merge(
141
+ :post_types => ["https://tent.io/types/post/status/v0.1.0", "https://tent.io/types/post/repost/v0.1.0"].join(',')
142
+ )
143
+
144
+ if (400...500).include?(res.status)
145
+ halt res.status
146
+ end
147
+
148
+ json res.body
149
+ end
150
+
151
+ get '/api/posts/:id' do
152
+ res = (params.delete('guest') ? guest_client : client).post.get(params[:id])
153
+ json res.body
154
+ end
155
+
156
+ post '/api/posts' do
157
+ data = JSON.parse(env['rack.input'].read)
158
+ env['rack.input'].rewind
159
+
160
+ data = {
161
+ :published_at => Time.now.to_i,
162
+ :type => data['type'] || "https://tent.io/types/post/status/v0.1.0",
163
+ :licenses => data['licenses'],
164
+ :mentions => data['mentions'],
165
+ :permissions => { public: true },
166
+ :content => data['content'] || {
167
+ :text => data['text'].to_s.slice(0...140)
168
+ }
169
+ }
170
+
171
+ res = (guest_client || client).post.create(data)
172
+
173
+ json res.body
174
+ end
175
+
176
+ delete '/api/posts/:id' do
177
+ res = (guest_client || client).post.delete(params[:id])
178
+ json res.body
179
+ end
180
+
181
+ get '/api/groups/count' do
182
+ res = (params.delete('guest') ? guest_client : client).group.count(params)
183
+ json res.body
184
+ end
185
+
186
+ get '/api/groups' do
187
+ res = (params.delete('guest') ? guest_client : client).group.list(params)
188
+ json res.body
189
+ end
190
+
191
+ post '/api/groups' do
192
+ data = JSON.parse(env['rack.input'].read)
193
+ env['rack.input'].rewind
194
+
195
+ res = (guest_client || client).group.create(data)
196
+ json res.body
197
+ end
198
+
199
+ get '/api/followers/count' do
200
+ res = (params.delete('guest') ? guest_client : client).follower.count(params)
201
+ json res.body
202
+ end
203
+
204
+ get '/api/followers' do
205
+ res = (params.delete('guest') ? guest_client : client).follower.list(params)
206
+ json res.body
207
+ end
208
+
209
+ put '/api/followers/:id' do
210
+ data = JSON.parse(env['rack.input'].read)
211
+ env['rack.input'].rewind
212
+
213
+ res = (guest_client || client).follower.update(params[:id], data)
214
+ json res.body
215
+ end
216
+
217
+ delete '/api/followers/:id' do
218
+ res = (guest_client || client).follower.delete(params[:id])
219
+ json res.body
220
+ end
221
+
222
+ get '/api/followings/count' do
223
+ res = (params.delete('guest') ? guest_client : client).following.count(params)
224
+ json res.body
225
+ end
226
+
227
+ get '/api/followings' do
228
+ res = (params.delete('guest') ? guest_client : client).following.list(params)
229
+ json res.body
230
+ end
231
+
232
+ post '/api/followings' do
233
+ data = JSON.parse(env['rack.input'].read)
234
+ env['rack.input'].rewind
235
+
236
+ res = (guest_client || client).following.create(data['entity'])
237
+ json res.body
238
+ end
239
+
240
+ put '/api/followings/:id' do
241
+ data = JSON.parse(env['rack.input'].read)
242
+ env['rack.input'].rewind
243
+
244
+ res = (guest_client || client).following.update(params[:id], data)
245
+ json res.body
246
+ end
247
+
248
+ delete '/api/followings/:id' do
249
+ res = (guest_client || client).following.delete(params[:id])
250
+ json res.body
251
+ end
252
+
253
+ get '/signout' do
254
+ session.clear
255
+ redirect full_path('/')
256
+ end
257
+
258
+ # Catch all for pushState routes
259
+ get '*' do
260
+ slim :application
261
+ end
262
+ end
263
+ end
@@ -0,0 +1,39 @@
1
+ require 'hashie'
2
+ require 'data_mapper'
3
+
4
+ module Tent
5
+ class Status
6
+ class User
7
+ include DataMapper::Resource
8
+
9
+ storage_names[:default] = 'users'
10
+
11
+ property :entity, URI, :key => true
12
+ property :server_uri, URI
13
+ property :app_id, String
14
+ property :app_mac, Json, :default => {}, :lazy => false
15
+ property :profile, Json, :default => {}
16
+ property :mac_key_id, String
17
+ property :mac_key, String
18
+ property :mac_algorithm, String
19
+ property :profile_info_types, Json, :default => %w{ https://tent.io/types/info/basic/v0.1.0 }
20
+ property :post_types, Json, :default => %w{ https://tent.io/types/post/status/v0.1.0 https://tent.io/types/post/repost/v0.1.0 }
21
+
22
+ def self.find_or_create(params)
23
+ first(:entity => params[:entity]) || create(params)
24
+ end
25
+
26
+ def auth_details
27
+ {
28
+ :mac_key_id => mac_key_id,
29
+ :mac_key => mac_key,
30
+ :mac_algorithm => mac_algorithm
31
+ }
32
+ end
33
+
34
+ def basic_profile
35
+ (profile || {})['https://tent.io/types/info/basic/v0.1.0'] || {}
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,25 @@
1
+ require 'tent-status/sprockets/helpers'
2
+
3
+ module Tent
4
+ class Status
5
+ module SprocketsEnvironment
6
+ def self.assets
7
+ return @assets if defined?(@assets)
8
+ puts 'SprocketsEnvironment loaded'
9
+ @assets = Sprockets::Environment.new do |env|
10
+ env.logger = Logger.new(STDOUT)
11
+ env.context_class.class_eval do
12
+ include SprocketsHelpers
13
+ end
14
+ end
15
+ @assets.register_engine('.slim', ::Slim::Template)
16
+
17
+ paths = %w{ javascripts stylesheets images }
18
+ paths.each do |path|
19
+ @assets.append_path(File.join(File.expand_path('../../../../', __FILE__), "assets/#{path}"))
20
+ end
21
+ @assets
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,5 @@
1
+ module SprocketsHelpers
2
+ def asset_path(source, options = {})
3
+ "./#{environment.find_asset(source).digest_path}"
4
+ end
5
+ end
@@ -0,0 +1,69 @@
1
+ doctype html
2
+
3
+ head
4
+ link href="https://#{domain_entity}/tent/profile" rel="https://tent.io/rels/profile"
5
+ meta name="viewport" content="width=device-width, initial-scale=1.0"
6
+ == csrf_meta_tag(:name => 'csrf-token')
7
+ link rel="stylesheet" type="text/css" href=asset_path("application.css")
8
+
9
+ body
10
+ .navbar.navbar-static-top
11
+ .navbar-inner
12
+ .brand
13
+ - if current_user || guest_user
14
+ a href==(current_user || guest_user).entity = (current_user || guest_user).entity.sub(%r{\Ahttps?://([^/]+).*?\z}) { |m| $1 }
15
+ - else
16
+ a href='https://tent.is' Tent.is
17
+
18
+ ul.nav
19
+ - if current_user || guest_user
20
+ li
21
+ a href=full_url('/') Posts
22
+ li
23
+ a href=full_url('/profile') Profile
24
+ li
25
+ a href=full_url('/followers') Followers
26
+ li
27
+ a href=full_url('/followings') Following
28
+ li
29
+ a href=full_url('/admin') Account
30
+ - else
31
+ li
32
+ a href='https://tent.is/blog' Blog
33
+
34
+ ul.nav.pull-right
35
+ - if current_user || guest_user
36
+ li
37
+ a href=full_url('/signout') Signout
38
+ - else
39
+ li
40
+ a href='https://tent.is/login' Login
41
+ li
42
+ a href='https://tent.is' Signup
43
+
44
+ .container#main
45
+ == yield if block_given?
46
+
47
+ footer.navbar-fixed-bottom
48
+ a href='https://tent.is/terms' Terms
49
+ ' |
50
+ a href='https://tent.is/privacy' Privacy
51
+ ' |
52
+ a href='https://tent.is/partners' Partners
53
+ ' |
54
+ a href='mailto:contact@tent.is' Contact
55
+ ' |
56
+ | &copy; 2012 Apollic Software, LLC
57
+
58
+ javascript:
59
+ var TentStatus = {
60
+ api_root: "#{{full_path('/api')}}",
61
+ url_root: "#{{full_path('/')}}",
62
+ authenticated: #{{!!(current_user || guest_user)}},
63
+ guest_authenticated: #{{!!guest_user}},
64
+ tent_api_root: "#{{tent_api_root}}",
65
+ current_entity: "#{{(current_user || guest_user) ? (current_user || guest_user).entity : nil}}",
66
+ domain_entity: "#{{domain_entity}}"
67
+ }
68
+
69
+ script src=asset_path("boot.js")
@@ -0,0 +1,8 @@
1
+ form.form-horizontal action=full_path('/oauth') method='POST'
2
+ == csrf_tag
3
+
4
+ .control-group
5
+ label.control-label Your Entity URI
6
+ .controls
7
+ input.input-xxlarge type='text' name='entity' placeholder='https://johndoe.example.com' &nbsp;
8
+ input.btn.btn-success type='submit' value='Authenticate'
@@ -0,0 +1,34 @@
1
+ # -*- encoding: utf-8 -*-
2
+ Gem::Specification.new do |gem|
3
+ gem.name = "tent-status"
4
+ gem.version = '0.0.1'
5
+ gem.authors = ["Jesse Stuart"]
6
+ gem.email = ["jessestuart@gmail.com"]
7
+ gem.description = %q{Tent app for 140 character posts. Uses Sinatra/Sprockets + CoffeeScript}
8
+ gem.summary = %q{Tent app for 140 character posts}
9
+ gem.homepage = ""
10
+
11
+ gem.files = `git ls-files`.split($/)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.require_paths = ["lib"]
15
+
16
+ gem.add_runtime_dependency 'tent-client'
17
+ gem.add_runtime_dependency 'sinatra'
18
+ gem.add_runtime_dependency 'rack_csrf'
19
+ gem.add_runtime_dependency 'data_mapper', '~> 1.2.0'
20
+ gem.add_runtime_dependency 'dm-postgres-adapter', '~> 1.2.0'
21
+ gem.add_runtime_dependency 'sprockets', '~> 2.0'
22
+ gem.add_runtime_dependency 'tilt'
23
+ gem.add_runtime_dependency 'sass'
24
+ gem.add_runtime_dependency 'coffee-script'
25
+ gem.add_runtime_dependency 'slim'
26
+ gem.add_runtime_dependency 'uglifier'
27
+ gem.add_runtime_dependency 'hogan_assets'
28
+ gem.add_runtime_dependency 'asset_sync', '~> 0.5.0'
29
+ gem.add_runtime_dependency 'hashie'
30
+
31
+ gem.add_development_dependency 'poltergeist'
32
+ gem.add_development_dependency 'evergreen'
33
+ gem.add_development_dependency 'kicker'
34
+ end