tent-status 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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