Require uploader privileges to upload media to API
[mediagoblin.git] / mediagoblin / federation / views.py
CommitLineData
c894b424 1import json
d4a21d7e 2import io
41599bf2 3import mimetypes
c894b424 4
c64fc16b 5from werkzeug.datastructures import FileStorage
d4a21d7e 6
7from mediagoblin.media_types import sniff_media
d7b3805f 8from mediagoblin.decorators import oauth_required
967df5ef 9from mediagoblin.federation.decorators import user_has_privilege
c894b424 10from mediagoblin.db.models import User, MediaEntry, MediaComment
a5682e89 11from mediagoblin.tools.response import redirect, json_response
c894b424 12from mediagoblin.meddleware.csrf import csrf_exempt
c64fc16b 13from mediagoblin.submit.lib import new_upload_entry
d7b3805f 14
247a3b78 15@oauth_required
a5682e89 16def profile(request, raw=False):
17 """ This is /api/user/<username>/profile - This will give profile info """
d7b3805f
JT
18 user = request.matchdict["username"]
19 requested_user = User.query.filter_by(username=user)
c64fc16b 20
d7b3805f
JT
21 # check if the user exists
22 if requested_user is None:
23 error = "No such 'user' with id '{0}'".format(user)
24 return json_response({"error": error}, status=404)
25
26 user = requested_user[0]
27
a5682e89 28 if raw:
29 return (user, user.serialize(request))
30
d7b3805f
JT
31 # user profiles are public so return information
32 return json_response(user.serialize(request))
33
247a3b78 34@oauth_required
a5682e89 35def user(request):
36 """ This is /api/user/<username> - This will get the user """
37 user, user_profile = profile(request, raw=True)
38 data = {
39 "nickname": user.username,
40 "updated": user.created.isoformat(),
41 "published": user.created.isoformat(),
42 "profile": user_profile
43 }
44
45 return json_response(data)
46
247a3b78 47@oauth_required
d4a21d7e 48@csrf_exempt
967df5ef 49@user_has_privilege(u'uploader')
d4a21d7e 50def uploads(request):
c64fc16b 51 """ Endpoint for file uploads """
d4a21d7e 52 user = request.matchdict["username"]
53 requested_user = User.query.filter_by(username=user)
54
55 if requested_user is None:
56 error = "No such 'user' with id '{0}'".format(user)
57 return json_response({"error": error}, status=404)
58
59 request.user = requested_user[0]
60 if request.method == "POST":
61 # Wrap the data in the werkzeug file wrapper
41599bf2
JT
62 mimetype = request.headers.get("Content-Type", "application/octal-stream")
63 filename = mimetypes.guess_all_extensions(mimetype)
64 filename = 'unknown' + filename[0] if filename else filename
d4a21d7e 65 file_data = FileStorage(
7810817c 66 stream=io.BytesIO(request.data),
41599bf2 67 filename=filename,
6781ff3c 68 content_type=mimetype
7810817c 69 )
70
71 # Find media manager
41599bf2 72 media_type, media_manager = sniff_media(file_data, filename)
d4a21d7e 73 entry = new_upload_entry(request.user)
7810817c 74 if hasattr(media_manager, "api_upload_request"):
75 return media_manager.api_upload_request(request, file_data, entry)
76 else:
c3b89feb 77 return json_response({"error": "Not yet implemented"}, status=501)
d4a21d7e 78
c3b89feb 79 return json_response({"error": "Not yet implemented"}, status=501)
d4a21d7e 80
247a3b78 81@oauth_required
c894b424 82@csrf_exempt
d7b3805f
JT
83def feed(request):
84 """ Handles the user's outbox - /api/user/<username>/feed """
85 user = request.matchdict["username"]
86 requested_user = User.query.filter_by(username=user)
87
88 # check if the user exists
89 if requested_user is None:
90 error = "No such 'user' with id '{0}'".format(user)
91 return json_response({"error": error}, status=404)
92
3c3fa5e7 93 request.user = requested_user[0]
6781ff3c 94 if request.data:
c894b424 95 data = json.loads(request.data)
6781ff3c
JT
96 else:
97 data = {"verb": None, "object": {}}
98
99 if request.method == "POST" and data["verb"] == "post":
c894b424
JT
100 obj = data.get("object", None)
101 if obj is None:
102 error = {"error": "Could not find 'object' element."}
103 return json_response(error, status=400)
c64fc16b 104
c894b424
JT
105 if obj.get("objectType", None) == "comment":
106 # post a comment
107 media = int(data["object"]["inReplyTo"]["id"])
c894b424
JT
108 comment = MediaComment(
109 media_entry=media,
110 author=request.user.id,
111 content=data["object"]["content"]
112 )
113 comment.save()
3c3fa5e7 114 data = {"verb": "post", "object": comment.serialize(request)}
115 return json_response(data)
7810817c 116
62dc7d3e 117 elif obj.get("objectType", None) == "image":
118 # Posting an image to the feed
119 # NB: This is currently just handing the image back until we have an
120 # to send the image to the actual feed
c64fc16b 121
62dc7d3e 122 media_id = int(data["object"]["id"])
123 media = MediaEntry.query.filter_by(id=media_id)
124 if media is None:
125 error = "No such 'image' with id '{0}'".format(id=media_id)
126 return json_response(error, status=404)
127 media = media[0]
c3b89feb
JT
128 return json_response({
129 "verb": "post",
130 "object": media.serialize(request)
131 })
62dc7d3e 132
c894b424 133 elif obj.get("objectType", None) is None:
62dc7d3e 134 # They need to tell us what type of object they're giving us.
c894b424
JT
135 error = {"error": "No objectType specified."}
136 return json_response(error, status=400)
137 else:
62dc7d3e 138 # Oh no! We don't know about this type of object (yet)
c64fc16b 139 error_message = "Unknown object type '{0}'.".format(
140 obj.get("objectType", None)
141 )
142
143 error = {"error": error_message}
c894b424
JT
144 return json_response(error, status=400)
145
6781ff3c
JT
146 elif request.method in ["PUT", "POST"] and data["verb"] == "update":
147 # Check we've got a valid object
148 obj = data.get("object", None)
149
150 if obj is None:
151 error = {"error": "Could not find 'object' element."}
152 return json_response(error, status=400)
153
154 if "objectType" not in obj:
155 error = {"error": "No objectType specified."}
156 return json_response(error, status=400)
157
158 if "id" not in obj:
159 error = {"error": "Object ID has not been specified."}
160 return json_response(error, status=400)
161
162 obj_id = obj["id"]
163
164 # Now try and find object
165 if obj["objectType"] == "comment":
166 comment = MediaComment.query.filter_by(id=obj_id)
167 if comment is None:
168 error = {"error": "No such 'comment' with id '{0}'.".format(obj_id)}
169 return json_response(error, status=400)
170 comment = comment[0]
171
172 # TODO: refactor this out to update/setting method on MediaComment
173 if obj.get("content", None) is not None:
174 comment.content = obj["content"]
175
176 comment.save()
177 activity = {
178 "verb": "update",
179 "object": comment.serialize(request),
180 }
181 return json_response(activity)
182
183 elif obj["objectType"] == "image":
184 image = MediaEntry.query.filter_by(id=obj_id)
185 if image is None:
186 error = {"error": "No such 'image' with the id '{0}'.".format(obj_id)}
187 return json_response(error, status=400)
188
189 image = image[0]
190
191 # TODO: refactor this out to update/setting method on MediaEntry
192 if obj.get("displayName", None) is not None:
193 image.title = obj["displayName"]
194
195 if obj.get("content", None) is not None:
196 image.description = obj["content"]
197
198 if obj.get("license", None) is not None:
199 # I think we might need some validation here
200 image.license = obj["license"]
201
202 image.save()
203 activity = {
204 "verb": "update",
205 "object": image.serialize(request),
206 }
207 return json_response(activity)
7810817c 208
c894b424 209 feed_url = request.urlgen(
6781ff3c
JT
210 "mediagoblin.federation.feed",
211 username=request.user.username,
212 qualified=True
213 )
c894b424
JT
214
215 feed = {
c64fc16b 216 "displayName": "Activities by {user}@{host}".format(
217 user=request.user.username,
218 host=request.host
219 ),
c894b424
JT
220 "objectTypes": ["activity"],
221 "url": feed_url,
222 "links": {
223 "first": {
224 "href": feed_url,
225 },
226 "self": {
227 "href": request.url,
228 },
229 "prev": {
230 "href": feed_url,
231 },
232 "next": {
233 "href": feed_url,
234 }
235 },
3c3fa5e7 236 "author": request.user.serialize(request),
c894b424
JT
237 "items": [],
238 }
c64fc16b 239
d7b3805f
JT
240
241 # Now lookup the user's feed.
c894b424
JT
242 for media in MediaEntry.query.all():
243 feed["items"].append({
244 "verb": "post",
245 "object": media.serialize(request),
3c3fa5e7 246 "actor": request.user.serialize(request),
247 "content": "{0} posted a picture".format(request.user.username),
c894b424
JT
248 "id": 1,
249 })
250 feed["items"][-1]["updated"] = feed["items"][-1]["object"]["updated"]
251 feed["items"][-1]["published"] = feed["items"][-1]["object"]["published"]
252 feed["items"][-1]["url"] = feed["items"][-1]["object"]["url"]
253 feed["totalItems"] = len(feed["items"])
254
255 return json_response(feed)
d7b3805f
JT
256
257@oauth_required
98596dd0 258def object(request, raw_obj=False):
5a2056f7 259 """ Lookup for a object type """
6781ff3c 260 object_type = request.matchdict["objectType"]
5a2056f7 261 uuid = request.matchdict["uuid"]
6781ff3c
JT
262 if object_type not in ["image"]:
263 error = "Unknown type: {0}".format(object_type)
c64fc16b 264 # not sure why this is 404, maybe ask evan. Maybe 400?
5a2056f7
JT
265 return json_response({"error": error}, status=404)
266
d461fbe5 267 media = MediaEntry.query.filter_by(slug=uuid).first()
5a2056f7
JT
268 if media is None:
269 # no media found with that uuid
6781ff3c 270 error = "Can't find a {0} with ID = {1}".format(object_type, uuid)
5a2056f7
JT
271 return json_response({"error": error}, status=404)
272
98596dd0 273 if raw_obj:
274 return media
275
bdde87a4 276 return json_response(media.serialize(request))
98596dd0 277
247a3b78 278@oauth_required
98596dd0 279def object_comments(request):
280 """ Looks up for the comments on a object """
281 media = object(request, raw_obj=True)
282 response = media
283 if isinstance(response, MediaEntry):
284 comments = response.serialize(request)
285 comments = comments.get("replies", {
6781ff3c
JT
286 "totalItems": 0,
287 "items": [],
288 "url": request.urlgen(
289 "mediagoblin.federation.object.comments",
290 objectType=media.objectType,
291 uuid=media.slug,
292 qualified=True
293 )
294 })
295
c894b424
JT
296 comments["displayName"] = "Replies to {0}".format(comments["url"])
297 comments["links"] = {
298 "first": comments["url"],
299 "self": comments["url"],
300 }
98596dd0 301 response = json_response(comments)
302
303 return response
a5682e89 304
305
306##
307# Well known
308##
309def host_meta(request):
310 """ This is /.well-known/host-meta - provides URL's to resources on server """
311 links = []
c64fc16b 312
a5682e89 313 # Client registration links
314 links.append({
315 "ref": "registration_endpoint",
316 "href": request.urlgen("mediagoblin.oauth.client_register", qualified=True),
317 })
318 links.append({
319 "ref": "http://apinamespace.org/oauth/request_token",
320 "href": request.urlgen("mediagoblin.oauth.request_token", qualified=True),
321 })
322 links.append({
323 "ref": "http://apinamespace.org/oauth/authorize",
324 "href": request.urlgen("mediagoblin.oauth.authorize", qualified=True),
325 })
326 links.append({
327 "ref": "http://apinamespace.org/oauth/access_token",
328 "href": request.urlgen("mediagoblin.oauth.access_token", qualified=True),
329 })
330
18297655 331 return json_response({"links": links})
a5682e89 332
333def whoami(request):
334 """ This is /api/whoami - This is a HTTP redirect to api profile """
335 profile = request.urlgen(
336 "mediagoblin.federation.user.profile",
337 username=request.user.username,
338 qualified=True
339 )
340
341 return redirect(request, location=profile)