tentd 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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