Commit | Line | Data |
---|---|---|
c3b89feb | 1 | |
c894b424 | 2 | import json |
d4a21d7e | 3 | import io |
41599bf2 | 4 | import mimetypes |
c894b424 | 5 | |
c64fc16b | 6 | from werkzeug.datastructures import FileStorage |
d4a21d7e | 7 | |
8 | from mediagoblin.media_types import sniff_media | |
d7b3805f | 9 | from mediagoblin.decorators import oauth_required |
c894b424 | 10 | from mediagoblin.db.models import User, MediaEntry, MediaComment |
a5682e89 | 11 | from mediagoblin.tools.response import redirect, json_response |
c894b424 | 12 | from mediagoblin.meddleware.csrf import csrf_exempt |
c64fc16b | 13 | from mediagoblin.submit.lib import new_upload_entry |
d7b3805f | 14 | |
247a3b78 | 15 | @oauth_required |
a5682e89 | 16 | def 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 | 35 | def 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 |
49 | def 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 |
82 | def 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 | 192 | def 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 | 213 | def 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 | ## | |
241 | def 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 | |
265 | def 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) |