Fix problem where feed posting wasn't returning correct object
[mediagoblin.git] / mediagoblin / federation / views.py
CommitLineData
c3b89feb 1
c894b424 2import json
d4a21d7e 3import io
41599bf2 4import mimetypes
c894b424 5
c64fc16b 6from werkzeug.datastructures import FileStorage
d4a21d7e 7
8from mediagoblin.media_types import sniff_media
d7b3805f 9from mediagoblin.decorators import oauth_required
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
49def uploads(request):
c64fc16b 50 """ Endpoint for file uploads """
d4a21d7e 51 user = request.matchdict["username"]
52 requested_user = User.query.filter_by(username=user)
53
54 if requested_user is None:
55 error = "No such 'user' with id '{0}'".format(user)
56 return json_response({"error": error}, status=404)
57
58 request.user = requested_user[0]
59 if request.method == "POST":
60 # Wrap the data in the werkzeug file wrapper
41599bf2
JT
61 mimetype = request.headers.get("Content-Type", "application/octal-stream")
62 filename = mimetypes.guess_all_extensions(mimetype)
63 filename = 'unknown' + filename[0] if filename else filename
d4a21d7e 64 file_data = FileStorage(
7810817c 65 stream=io.BytesIO(request.data),
41599bf2 66 filename=filename,
7810817c 67 content_type=request.headers.get("Content-Type", "application/octal-stream")
68 )
69
70 # Find media manager
41599bf2 71 media_type, media_manager = sniff_media(file_data, filename)
d4a21d7e 72 entry = new_upload_entry(request.user)
7810817c 73 if hasattr(media_manager, "api_upload_request"):
74 return media_manager.api_upload_request(request, file_data, entry)
75 else:
c3b89feb 76 return json_response({"error": "Not yet implemented"}, status=501)
d4a21d7e 77
c3b89feb 78 return json_response({"error": "Not yet implemented"}, status=501)
d4a21d7e 79
247a3b78 80@oauth_required
c894b424 81@csrf_exempt
d7b3805f
JT
82def feed(request):
83 """ Handles the user's outbox - /api/user/<username>/feed """
84 user = request.matchdict["username"]
85 requested_user = User.query.filter_by(username=user)
86
87 # check if the user exists
88 if requested_user is None:
89 error = "No such 'user' with id '{0}'".format(user)
90 return json_response({"error": error}, status=404)
91
3c3fa5e7 92 request.user = requested_user[0]
c894b424
JT
93
94 if request.method == "POST":
95 data = json.loads(request.data)
96 obj = data.get("object", None)
97 if obj is None:
98 error = {"error": "Could not find 'object' element."}
99 return json_response(error, status=400)
c64fc16b 100
c894b424
JT
101 if obj.get("objectType", None) == "comment":
102 # post a comment
103 media = int(data["object"]["inReplyTo"]["id"])
c894b424
JT
104 comment = MediaComment(
105 media_entry=media,
106 author=request.user.id,
107 content=data["object"]["content"]
108 )
109 comment.save()
3c3fa5e7 110 data = {"verb": "post", "object": comment.serialize(request)}
111 return json_response(data)
7810817c 112
62dc7d3e 113 elif obj.get("objectType", None) == "image":
114 # Posting an image to the feed
115 # NB: This is currently just handing the image back until we have an
116 # to send the image to the actual feed
c64fc16b 117
62dc7d3e 118 media_id = int(data["object"]["id"])
119 media = MediaEntry.query.filter_by(id=media_id)
120 if media is None:
121 error = "No such 'image' with id '{0}'".format(id=media_id)
122 return json_response(error, status=404)
123 media = media[0]
c3b89feb
JT
124 return json_response({
125 "verb": "post",
126 "object": media.serialize(request)
127 })
62dc7d3e 128
c894b424 129 elif obj.get("objectType", None) is None:
62dc7d3e 130 # They need to tell us what type of object they're giving us.
c894b424
JT
131 error = {"error": "No objectType specified."}
132 return json_response(error, status=400)
133 else:
62dc7d3e 134 # Oh no! We don't know about this type of object (yet)
c64fc16b 135 error_message = "Unknown object type '{0}'.".format(
136 obj.get("objectType", None)
137 )
138
139 error = {"error": error_message}
c894b424
JT
140 return json_response(error, status=400)
141
7810817c 142
c894b424
JT
143 feed_url = request.urlgen(
144 "mediagoblin.federation.feed",
3c3fa5e7 145 username=request.user.username,
c894b424
JT
146 qualified=True
147 )
148
149 feed = {
c64fc16b 150 "displayName": "Activities by {user}@{host}".format(
151 user=request.user.username,
152 host=request.host
153 ),
c894b424
JT
154 "objectTypes": ["activity"],
155 "url": feed_url,
156 "links": {
157 "first": {
158 "href": feed_url,
159 },
160 "self": {
161 "href": request.url,
162 },
163 "prev": {
164 "href": feed_url,
165 },
166 "next": {
167 "href": feed_url,
168 }
169 },
3c3fa5e7 170 "author": request.user.serialize(request),
c894b424
JT
171 "items": [],
172 }
c64fc16b 173
d7b3805f
JT
174
175 # Now lookup the user's feed.
c894b424
JT
176 for media in MediaEntry.query.all():
177 feed["items"].append({
178 "verb": "post",
179 "object": media.serialize(request),
3c3fa5e7 180 "actor": request.user.serialize(request),
181 "content": "{0} posted a picture".format(request.user.username),
c894b424
JT
182 "id": 1,
183 })
184 feed["items"][-1]["updated"] = feed["items"][-1]["object"]["updated"]
185 feed["items"][-1]["published"] = feed["items"][-1]["object"]["published"]
186 feed["items"][-1]["url"] = feed["items"][-1]["object"]["url"]
187 feed["totalItems"] = len(feed["items"])
188
189 return json_response(feed)
d7b3805f
JT
190
191@oauth_required
98596dd0 192def object(request, raw_obj=False):
5a2056f7
JT
193 """ Lookup for a object type """
194 objectType = request.matchdict["objectType"]
195 uuid = request.matchdict["uuid"]
196 if objectType not in ["image"]:
197 error = "Unknown type: {0}".format(objectType)
c64fc16b 198 # not sure why this is 404, maybe ask evan. Maybe 400?
5a2056f7
JT
199 return json_response({"error": error}, status=404)
200
d461fbe5 201 media = MediaEntry.query.filter_by(slug=uuid).first()
5a2056f7
JT
202 if media is None:
203 # no media found with that uuid
204 error = "Can't find a {0} with ID = {1}".format(objectType, uuid)
205 return json_response({"error": error}, status=404)
206
98596dd0 207 if raw_obj:
208 return media
209
bdde87a4 210 return json_response(media.serialize(request))
98596dd0 211
247a3b78 212@oauth_required
98596dd0 213def object_comments(request):
214 """ Looks up for the comments on a object """
215 media = object(request, raw_obj=True)
216 response = media
217 if isinstance(response, MediaEntry):
218 comments = response.serialize(request)
219 comments = comments.get("replies", {
220 "totalItems": 0,
221 "items": [],
222 "url": request.urlgen(
223 "mediagoblin.federation.object.comments",
224 objectType=media.objectType,
225 uuid=media.slug,
226 qualified=True)
227 })
c894b424
JT
228 comments["displayName"] = "Replies to {0}".format(comments["url"])
229 comments["links"] = {
230 "first": comments["url"],
231 "self": comments["url"],
232 }
98596dd0 233 response = json_response(comments)
234
235 return response
a5682e89 236
237
238##
239# Well known
240##
241def host_meta(request):
242 """ This is /.well-known/host-meta - provides URL's to resources on server """
243 links = []
c64fc16b 244
a5682e89 245 # Client registration links
246 links.append({
247 "ref": "registration_endpoint",
248 "href": request.urlgen("mediagoblin.oauth.client_register", qualified=True),
249 })
250 links.append({
251 "ref": "http://apinamespace.org/oauth/request_token",
252 "href": request.urlgen("mediagoblin.oauth.request_token", qualified=True),
253 })
254 links.append({
255 "ref": "http://apinamespace.org/oauth/authorize",
256 "href": request.urlgen("mediagoblin.oauth.authorize", qualified=True),
257 })
258 links.append({
259 "ref": "http://apinamespace.org/oauth/access_token",
260 "href": request.urlgen("mediagoblin.oauth.access_token", qualified=True),
261 })
262
18297655 263 return json_response({"links": links})
a5682e89 264
265def whoami(request):
266 """ This is /api/whoami - This is a HTTP redirect to api profile """
267 profile = request.urlgen(
268 "mediagoblin.federation.user.profile",
269 username=request.user.username,
270 qualified=True
271 )
272
273 return redirect(request, location=profile)