tentd 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 (101) hide show
  1. data/.gitignore +18 -0
  2. data/.rspec +1 -0
  3. data/.travis.yml +8 -0
  4. data/Gemfile +9 -0
  5. data/Guardfile +6 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +49 -0
  8. data/Rakefile +8 -0
  9. data/bin/tent-server +3 -0
  10. data/lib/tentd.rb +31 -0
  11. data/lib/tentd/api.rb +58 -0
  12. data/lib/tentd/api/apps.rb +196 -0
  13. data/lib/tentd/api/authentication_finalize.rb +12 -0
  14. data/lib/tentd/api/authentication_lookup.rb +27 -0
  15. data/lib/tentd/api/authentication_verification.rb +50 -0
  16. data/lib/tentd/api/authorizable.rb +21 -0
  17. data/lib/tentd/api/authorization.rb +14 -0
  18. data/lib/tentd/api/core_profile_data.rb +45 -0
  19. data/lib/tentd/api/followers.rb +218 -0
  20. data/lib/tentd/api/followings.rb +241 -0
  21. data/lib/tentd/api/groups.rb +161 -0
  22. data/lib/tentd/api/middleware.rb +32 -0
  23. data/lib/tentd/api/posts.rb +373 -0
  24. data/lib/tentd/api/profile.rb +78 -0
  25. data/lib/tentd/api/router.rb +123 -0
  26. data/lib/tentd/api/router/caching_headers.rb +49 -0
  27. data/lib/tentd/api/router/extract_params.rb +88 -0
  28. data/lib/tentd/api/router/serialize_response.rb +38 -0
  29. data/lib/tentd/api/user_lookup.rb +10 -0
  30. data/lib/tentd/core_ext/hash/slice.rb +29 -0
  31. data/lib/tentd/datamapper/array_property.rb +23 -0
  32. data/lib/tentd/datamapper/query.rb +19 -0
  33. data/lib/tentd/json_patch.rb +181 -0
  34. data/lib/tentd/model.rb +30 -0
  35. data/lib/tentd/model/app.rb +68 -0
  36. data/lib/tentd/model/app_authorization.rb +113 -0
  37. data/lib/tentd/model/follower.rb +105 -0
  38. data/lib/tentd/model/following.rb +100 -0
  39. data/lib/tentd/model/group.rb +24 -0
  40. data/lib/tentd/model/mention.rb +19 -0
  41. data/lib/tentd/model/notification_subscription.rb +56 -0
  42. data/lib/tentd/model/permissible.rb +227 -0
  43. data/lib/tentd/model/permission.rb +28 -0
  44. data/lib/tentd/model/post.rb +178 -0
  45. data/lib/tentd/model/post_attachment.rb +27 -0
  46. data/lib/tentd/model/post_version.rb +64 -0
  47. data/lib/tentd/model/profile_info.rb +80 -0
  48. data/lib/tentd/model/random_public_id.rb +46 -0
  49. data/lib/tentd/model/serializable.rb +58 -0
  50. data/lib/tentd/model/type_properties.rb +36 -0
  51. data/lib/tentd/model/user.rb +39 -0
  52. data/lib/tentd/model/user_scoped.rb +14 -0
  53. data/lib/tentd/notifications.rb +13 -0
  54. data/lib/tentd/notifications/girl_friday.rb +30 -0
  55. data/lib/tentd/notifications/sidekiq.rb +50 -0
  56. data/lib/tentd/tent_type.rb +20 -0
  57. data/lib/tentd/tent_version.rb +41 -0
  58. data/lib/tentd/version.rb +3 -0
  59. data/spec/fabricators/app_authorizations_fabricator.rb +5 -0
  60. data/spec/fabricators/apps_fabricator.rb +11 -0
  61. data/spec/fabricators/followers_fabricator.rb +14 -0
  62. data/spec/fabricators/followings_fabricator.rb +17 -0
  63. data/spec/fabricators/groups_fabricator.rb +3 -0
  64. data/spec/fabricators/mentions_fabricator.rb +3 -0
  65. data/spec/fabricators/notification_subscriptions_fabricator.rb +4 -0
  66. data/spec/fabricators/permissions_fabricator.rb +1 -0
  67. data/spec/fabricators/post_attachments_fabricator.rb +8 -0
  68. data/spec/fabricators/post_versions_fabricator.rb +12 -0
  69. data/spec/fabricators/posts_fabricator.rb +12 -0
  70. data/spec/fabricators/profile_infos_fabricator.rb +30 -0
  71. data/spec/integration/api/apps_spec.rb +466 -0
  72. data/spec/integration/api/followers_spec.rb +535 -0
  73. data/spec/integration/api/followings_spec.rb +688 -0
  74. data/spec/integration/api/groups_spec.rb +207 -0
  75. data/spec/integration/api/posts_spec.rb +874 -0
  76. data/spec/integration/api/profile_spec.rb +285 -0
  77. data/spec/integration/api/router_spec.rb +102 -0
  78. data/spec/integration/model/app_authorization_spec.rb +59 -0
  79. data/spec/integration/model/app_spec.rb +63 -0
  80. data/spec/integration/model/follower_spec.rb +344 -0
  81. data/spec/integration/model/following_spec.rb +97 -0
  82. data/spec/integration/model/group_spec.rb +39 -0
  83. data/spec/integration/model/notification_subscription_spec.rb +145 -0
  84. data/spec/integration/model/post_spec.rb +658 -0
  85. data/spec/spec_helper.rb +37 -0
  86. data/spec/support/expect_server.rb +3 -0
  87. data/spec/support/json_request.rb +54 -0
  88. data/spec/support/with_constant.rb +23 -0
  89. data/spec/support/with_warnings.rb +6 -0
  90. data/spec/unit/api/authentication_finalize_spec.rb +45 -0
  91. data/spec/unit/api/authentication_lookup_spec.rb +65 -0
  92. data/spec/unit/api/authentication_verification_spec.rb +50 -0
  93. data/spec/unit/api/authorizable_spec.rb +50 -0
  94. data/spec/unit/api/authorization_spec.rb +44 -0
  95. data/spec/unit/api/caching_headers_spec.rb +121 -0
  96. data/spec/unit/core_profile_data_spec.rb +64 -0
  97. data/spec/unit/json_patch_spec.rb +407 -0
  98. data/spec/unit/tent_type_spec.rb +28 -0
  99. data/spec/unit/tent_version_spec.rb +68 -0
  100. data/tentd.gemspec +36 -0
  101. metadata +435 -0
@@ -0,0 +1,161 @@
1
+ module TentD
2
+ class API
3
+ class Groups
4
+ include Router
5
+
6
+ class AuthorizeRead < Middleware
7
+ def action(env)
8
+ authorize_env!(env, :read_groups)
9
+ env
10
+ end
11
+ end
12
+
13
+ class AuthorizeWrite < Middleware
14
+ def action(env)
15
+ authorize_env!(env, :write_groups)
16
+ env
17
+ end
18
+ end
19
+
20
+ class GetActualId < Middleware
21
+ def action(env)
22
+ [:group_id, :before_id, :since_id].each do |id_key|
23
+ if env.params[id_key]
24
+ if g = Model::Group.first(:public_id => env.params[id_key])
25
+ env.params[id_key] = g.id
26
+ else
27
+ env.params[id_key] = nil
28
+ end
29
+ end
30
+ end
31
+ env
32
+ end
33
+ end
34
+
35
+ class GetCount < Middleware
36
+ def action(env)
37
+ env.params.return_count = true
38
+ env
39
+ end
40
+ end
41
+
42
+ class GetAll < Middleware
43
+ def action(env)
44
+ conditions = {}
45
+ conditions[:id.lt] = env.params.before_id if env.params.before_id
46
+ conditions[:id.gt] = env.params.since_id if env.params.since_id
47
+ conditions[:limit] = [env.params.limit.to_i, MAX_PER_PAGE].min if env.params.limit
48
+ conditions[:limit] ||= PER_PAGE
49
+ if env.params.return_count
50
+ env.response = Model::Group.count(conditions)
51
+ else
52
+ conditions[:order] = :id.desc
53
+ if conditions[:limit] == 0
54
+ env.response = []
55
+ else
56
+ env.response = Model::Group.all(conditions)
57
+ end
58
+ end
59
+ env
60
+ end
61
+ end
62
+
63
+ class GetOne < Middleware
64
+ def action(env)
65
+ if group = Model::Group.first(:id => env.params[:group_id])
66
+ env['response'] = group
67
+ end
68
+ env
69
+ end
70
+ end
71
+
72
+ class Update < Middleware
73
+ def action(env)
74
+ if group = Model::Group.first(:id => env.params[:group_id])
75
+ group.update(:name => env.params.data.name)
76
+ env['response'] = group.reload
77
+ end
78
+ env
79
+ end
80
+ end
81
+
82
+ class Create < Middleware
83
+ def action(env)
84
+ group_attributes = env.params[:data]
85
+ if group = Model::Group.create(group_attributes)
86
+ env.response = group
87
+ env.notify_action = 'create'
88
+ env.notify_instance = group
89
+ end
90
+ env
91
+ end
92
+ end
93
+
94
+ class Destroy < Middleware
95
+ def action(env)
96
+ if (group = Model::Group.first(:id => env.params.group_id)) && group.destroy
97
+ env.response = ''
98
+ env.notify_action = 'delete'
99
+ env.notify_instance = group
100
+ end
101
+ env
102
+ end
103
+ end
104
+
105
+ class Notify < Middleware
106
+ def action(env)
107
+ return env unless group = env.notify_instance
108
+ post = Model::Post.create(
109
+ :type => 'https://tent.io/types/post/group/v0.1.0',
110
+ :entity => env['tent.entity'],
111
+ :content => {
112
+ :id => group.public_id,
113
+ :name => group.name,
114
+ :action => env.notify_action
115
+ }
116
+ )
117
+ Notifications.trigger(:type => post.type.uri, :post_id => post.id)
118
+ env
119
+ end
120
+ end
121
+
122
+ get '/groups' do |b|
123
+ b.use AuthorizeRead
124
+ b.use GetActualId
125
+ b.use GetAll
126
+ end
127
+
128
+ get '/groups/count' do |b|
129
+ b.use AuthorizeRead
130
+ b.use GetActualId
131
+ b.use GetCount
132
+ b.use GetAll
133
+ end
134
+
135
+ get '/groups/:group_id' do |b|
136
+ b.use AuthorizeRead
137
+ b.use GetActualId
138
+ b.use GetOne
139
+ end
140
+
141
+ put '/groups/:group_id' do |b|
142
+ b.use AuthorizeWrite
143
+ b.use GetActualId
144
+ b.use Update
145
+ end
146
+
147
+ post '/groups' do |b|
148
+ b.use AuthorizeWrite
149
+ b.use Create
150
+ b.use Notify
151
+ end
152
+
153
+ delete '/groups/:group_id' do |b|
154
+ b.use AuthorizeWrite
155
+ b.use GetActualId
156
+ b.use Destroy
157
+ b.use Notify
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,32 @@
1
+ require 'hashie'
2
+
3
+ module TentD
4
+ class API
5
+ class Middleware
6
+ include Authorizable
7
+
8
+ def initialize(app)
9
+ @app = app
10
+ end
11
+
12
+ def call(env)
13
+ env = Hashie::Mash.new(env) unless env.kind_of?(Hashie::Mash)
14
+ response = action(env)
15
+ response.kind_of?(Hash) ? @app.call(response) : response
16
+ rescue Unauthorized
17
+ [403, {}, ['Unauthorized']]
18
+ rescue DataMapper::SaveFailureError, DataObjects::IntegrityError
19
+ [422, {}, ['Invalid Attributes']]
20
+ rescue Exception => e
21
+ if ENV['RACK_ENV'] == 'test'
22
+ raise
23
+ elsif defined?(Airbrake)
24
+ Airbrake.notify_or_ignore(e, :rack_env => env)
25
+ else
26
+ puts $!.inspect, $@
27
+ end
28
+ [500, {}, ['Internal Server Error']]
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,373 @@
1
+ module TentD
2
+ class API
3
+ class Posts
4
+ include Router
5
+
6
+ class GetActualId < Middleware
7
+ def action(env)
8
+ id_mapping = [:post_id, :since_id, :before_id].select { |key| env.params.has_key?(key) }.inject({}) { |memo, key|
9
+ memo[env.params[key]] = key
10
+ env.params[key] = nil
11
+ memo
12
+ }
13
+ posts = Model::Post.all(:public_id => id_mapping.keys, :fields => [:id, :public_id])
14
+ posts.each do |post|
15
+ key = id_mapping[post.public_id]
16
+ env.params[key] = post.id
17
+ end
18
+ env
19
+ end
20
+ end
21
+
22
+ class GetOne < Middleware
23
+ def action(env)
24
+ if authorize_env?(env, :read_posts)
25
+ conditions = { :id => env.params.post_id }
26
+ unless env.current_auth.post_types.include?('all')
27
+ conditions[:type_base] = env.current_auth.post_types.map { |t| TentType.new(t).base }
28
+ end
29
+
30
+ if env.params.version
31
+ conditions[:fields] = [:id]
32
+ end
33
+
34
+ post = Model::Post.first(conditions)
35
+ else
36
+ post = Model::Post.find_with_permissions(env.params.post_id, env.current_auth)
37
+ end
38
+ if post
39
+ if env.params.version
40
+ if post_version = post.versions.first(:version => env.params.version.to_i)
41
+ post = post_version
42
+ else
43
+ post = nil
44
+ end
45
+ end
46
+
47
+ env.response = post
48
+ end
49
+ env
50
+ end
51
+ end
52
+
53
+ class GetCount < Middleware
54
+ def action(env)
55
+ env.params.return_count = true
56
+ env
57
+ end
58
+ end
59
+
60
+ class GetFeed < Middleware
61
+ def action(env)
62
+ if authorize_env?(env, :read_posts)
63
+ conditions = {}
64
+ non_public_conditions = {}
65
+ conditions[:id.gt] = env.params.since_id if env.params.since_id
66
+ conditions[:id.lt] = env.params.before_id if env.params.before_id
67
+ conditions[:published_at.gt] = Time.at(env.params.since_time.to_i) if env.params.since_time
68
+ conditions[:published_at.lt] = Time.at(env.params.before_time.to_i) if env.params.before_time
69
+ conditions[:entity] = env.params.entity if env.params.entity
70
+ if env.params.mentioned_post && env.params.mentioned_entity
71
+ conditions[:mentions] = {
72
+ :mentioned_post_id => env.params.mentioned_post,
73
+ :entity => env.params.mentioned_entity
74
+ }
75
+ end
76
+ if env.params.post_types
77
+ types = env.params.post_types.split(',').map { |t| TentType.new(URI.unescape(t)) }
78
+
79
+ non_public_conditions[:type_base] = types.select do |type|
80
+ env.current_auth.post_types.include?('all') ||
81
+ env.current_auth.post_types.include?(type.uri)
82
+ end.map(&:base)
83
+ non_public_conditions[:type_base] = nil unless non_public_conditions[:type_base].any?
84
+
85
+ conditions[:type_base] = types.map(&:base)
86
+
87
+ elsif !env.current_auth.post_types.include?('all')
88
+ non_public_conditions[:type_base] = env.current_auth.post_types.map { |t| TentType.new(t).base }
89
+ end
90
+ if env.params.limit
91
+ conditions[:limit] = [env.params.limit.to_i, TentD::API::MAX_PER_PAGE].min
92
+ else
93
+ conditions[:limit] = TentD::API::PER_PAGE
94
+ end
95
+
96
+ if env.params.return_count
97
+ conditions[:original] = true
98
+ env.response = Model::Post.all(conditions.merge(non_public_conditions))
99
+ env.response += Model::Post.all(conditions.merge(:public => true))
100
+ env.response = env.response.count
101
+ else
102
+ conditions[:order] = :published_at.desc
103
+ if conditions[:limit] == 0
104
+ env.response = []
105
+ else
106
+ env.response = Model::Post.all(conditions.merge(non_public_conditions))
107
+ env.response += Model::Post.all(conditions.merge(:public => true))
108
+ end
109
+ end
110
+ else
111
+ env.response = Model::Post.fetch_with_permissions(env.params, env.current_auth)
112
+ end
113
+ env
114
+ end
115
+ end
116
+
117
+ class CreatePost < Middleware
118
+ def action(env)
119
+ authorize_post!(env)
120
+ set_app_details(env.params.data)
121
+ set_publicity(env.params.data)
122
+ parse_times(env.params.data)
123
+ data = env.params[:data].slice(*whitelisted_attributes(env))
124
+ data.public_id = env.params.data.id if env.params.data.id
125
+ post = Model::Post.create(data)
126
+ post.assign_permissions(env.params.data.permissions) if post.original
127
+ env['response'] = post
128
+ env
129
+ end
130
+
131
+ private
132
+
133
+ def authorize_post!(env)
134
+ post = env.params.data
135
+ if auth_is_publisher?(env.current_auth, post)
136
+ post.following_id = env.current_auth.id if env.current_auth.kind_of?(Model::Following)
137
+ env.authorized_scopes << :write_posts
138
+ elsif anonymous_publisher?(env.current_auth, post) && post != env['tent.entity']
139
+ env.authorized_scopes << :write_posts
140
+ elsif env.authorized_scopes.include?(:import_posts)
141
+ post.entity ||= env['tent.entity']
142
+ post.app ||= env.current_auth.app
143
+ if post.following_id && following = Model::Following.first(:public_id => post.following_id)
144
+ post.following_id = following.id
145
+ end
146
+ elsif env.current_auth.respond_to?(:app)
147
+ post.entity = env['tent.entity']
148
+ post.app = env.current_auth.app
149
+ post.following_id = nil
150
+ end
151
+ post.original = post.entity == env['tent.entity']
152
+ authorize_env!(env, :write_posts)
153
+ end
154
+
155
+ def whitelisted_attributes(env)
156
+ attrs = Model::Post.write_attributes
157
+ attrs += [:app_id] if env.current_auth.respond_to?(:app)
158
+ attrs += [:received_at] if env.authorized_scopes.include?(:import_posts)
159
+ attrs
160
+ end
161
+
162
+ def auth_is_publisher?(auth, post)
163
+ auth.respond_to?(:entity) && auth.entity == post.entity
164
+ end
165
+
166
+ def anonymous_publisher?(auth, post)
167
+ !auth && post.entity && !Model::Following.first(:entity => post.entity, :fields => [:id])
168
+ end
169
+
170
+ def set_app_details(post)
171
+ if app = post.delete('app')
172
+ post.app_url = app.url
173
+ post.app_name = app.name
174
+ post.app_id = app.id if app.id
175
+ end
176
+ end
177
+
178
+ def set_publicity(post)
179
+ post.public = post.permissions.public if post.permissions
180
+ end
181
+
182
+ def parse_times(post)
183
+ post.published_at = Time.at(post.published_at.to_i) if post.published_at
184
+ post.received_at = Time.at(post.received_at.to_i) if post.received_at
185
+ end
186
+
187
+ def assign_permissions(post, permissions)
188
+ return unless post.original && permissions
189
+ if permissions.groups && permissions.groups.kind_of?(Array)
190
+ permissions.groups.each do |g|
191
+ next unless g.id
192
+ group = Model::Group.first(:public_id => g.id, :fields => [:id])
193
+ post.permissions.create(:group => group) if group
194
+ end
195
+ end
196
+
197
+ if permissions.entities && permissions.entities.kind_of?(Hash)
198
+ permissions.entities.each do |entity,visible|
199
+ next unless visible
200
+ followers = Model::Follower.all(:entity => entity, :fields => [:id])
201
+ followers.each do |follower|
202
+ post.permissions.create(:follower_access => follower)
203
+ end
204
+ followings = Model::Following.all(:entity => entity, :fields => [:id])
205
+ followings.each do |following|
206
+ post.permissions.create(:following => following)
207
+ end
208
+ end
209
+ end
210
+ end
211
+ end
212
+
213
+ class Update < Middleware
214
+ def action(env)
215
+ authorize_env!(env, :write_posts)
216
+ if post = TentD::Model::Post.first(:id => env.params.post_id)
217
+ version = post.latest_version(:fields => [:id])
218
+ post.update(env.params.data.slice(:content, :licenses, :mentions, :views))
219
+
220
+ if env.params.attachments.kind_of?(Array)
221
+ Model::PostAttachment.all(:post_id => post.id).update(:post_id => nil, :post_version_id => version.id)
222
+ end
223
+
224
+ env.response = post
225
+ end
226
+ env
227
+ end
228
+ end
229
+
230
+ class Destroy < Middleware
231
+ def action(env)
232
+ authorize_env!(env, :write_posts)
233
+ if (post = TentD::Model::Post.first(:id => env.params.post_id)) && post.destroy
234
+ raise Unauthorized unless post.original
235
+ env.response = ''
236
+ env.notify_deleted_post = post
237
+ end
238
+ env
239
+ end
240
+ end
241
+
242
+ class CreateAttachments < Middleware
243
+ def action(env)
244
+ return env unless env.params.attachments.kind_of?(Array) && env.response
245
+ post = env.response
246
+ version = post.latest_version(:fields => [:id])
247
+ env.params.attachments.each do |attachment|
248
+ Model::PostAttachment.create(:post => post,
249
+ :post_version => version,
250
+ :type => attachment.type,
251
+ :category => attachment.name, :name => attachment.filename,
252
+ :data => Base64.encode64(attachment.tempfile.read), :size => attachment.tempfile.size)
253
+ end
254
+ env.response.reload
255
+ env
256
+ end
257
+ end
258
+
259
+ class Notify < Middleware
260
+ def action(env)
261
+ if deleted_post = env.notify_deleted_post
262
+ post = Model::Post.create(
263
+ :type => 'https://tent.io/types/post/delete/v0.1.0',
264
+ :entity => env['tent.entity'],
265
+ :original => true,
266
+ :content => {
267
+ :id => deleted_post.public_id
268
+ }
269
+ )
270
+ Model::Permission.copy(deleted_post, post)
271
+ else
272
+ return env unless (post = env.response) && post.kind_of?(Model::Post)
273
+ end
274
+ Notifications.trigger(:type => post.type.uri, :post_id => post.id)
275
+ env
276
+ end
277
+ end
278
+
279
+ class GetAttachment < Middleware
280
+ def action(env)
281
+ return env unless env.response
282
+ type = env['HTTP_ACCEPT'].split(/;|,/).first if env['HTTP_ACCEPT']
283
+ attachment = env.response.attachments.first(:type => type, :name => env.params.attachment_name, :fields => [:data])
284
+ if attachment
285
+ env.response = Base64.decode64(attachment.data)
286
+ env['response.type'] = type
287
+ else
288
+ env.response = nil
289
+ end
290
+ env
291
+ end
292
+ end
293
+
294
+ class ConfirmFollowing < Middleware
295
+ def action(env)
296
+ if Model::Following.first(:public_id => env.params.following_id)
297
+ [200, { 'Content-Type' => 'text/plain' }, [env.params.challenge]]
298
+ else
299
+ [404, {}, []]
300
+ end
301
+ end
302
+ end
303
+
304
+ class TriggerUpdates < Middleware
305
+ def action(env)
306
+ post = env.response
307
+ if post && post.following && post.following == env.current_auth
308
+ case post.type.base
309
+ when 'https://tent.io/types/post/profile'
310
+ Notifications.update_following_profile(:following_id => post.following.id)
311
+ when 'https://tent.io/types/post/delete'
312
+ if deleted_post = Model::Post.first(:public_id => post.content.id, :following_id => env.current_auth.id)
313
+ deleted_post.destroy
314
+ end
315
+ end
316
+ end
317
+ env
318
+ end
319
+ end
320
+
321
+ get '/posts/count' do |b|
322
+ b.use GetCount
323
+ b.use GetFeed
324
+ end
325
+
326
+ get '/posts/:post_id' do |b|
327
+ b.use GetActualId
328
+ b.use GetOne
329
+ end
330
+
331
+ get '/posts/:post_id/attachments/:attachment_name' do |b|
332
+ b.use GetActualId
333
+ b.use GetOne
334
+ b.use GetAttachment
335
+ end
336
+
337
+ get '/posts' do |b|
338
+ b.use GetActualId
339
+ b.use GetFeed
340
+ end
341
+
342
+ post '/posts' do |b|
343
+ b.use CreatePost
344
+ b.use CreateAttachments
345
+ b.use Notify
346
+ end
347
+
348
+ post '/notifications/:following_id' do |b|
349
+ b.use CreatePost
350
+ b.use CreateAttachments
351
+ b.use Notify
352
+ b.use TriggerUpdates
353
+ end
354
+
355
+ get '/notifications/:following_id' do |b|
356
+ b.use ConfirmFollowing
357
+ end
358
+
359
+ put '/posts/:post_id' do |b|
360
+ b.use GetActualId
361
+ b.use Update
362
+ b.use CreateAttachments
363
+ b.use Notify
364
+ end
365
+
366
+ delete '/posts/:post_id' do |b|
367
+ b.use GetActualId
368
+ b.use Destroy
369
+ b.use Notify
370
+ end
371
+ end
372
+ end
373
+ end