# GNU MediaGoblin -- federated, autonomous media hosting
# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
import json
import io
import mimetypes
from werkzeug.datastructures import FileStorage
from mediagoblin.media_types import sniff_media
from mediagoblin.decorators import oauth_required
from mediagoblin.federation.decorators import user_has_privilege
from mediagoblin.db.models import User, MediaEntry, MediaComment
from mediagoblin.tools.response import redirect, json_response
from mediagoblin.meddleware.csrf import csrf_exempt
from mediagoblin.submit.lib import new_upload_entry
@oauth_required
def profile(request, raw=False):
""" This is /api/user//profile - This will give profile info """
user = request.matchdict["username"]
requested_user = User.query.filter_by(username=user)
# check if the user exists
if requested_user is None:
error = "No such 'user' with id '{0}'".format(user)
return json_response({"error": error}, status=404)
user = requested_user[0]
if raw:
return (user, user.serialize(request))
# user profiles are public so return information
return json_response(user.serialize(request))
@oauth_required
def user(request):
""" This is /api/user/ - This will get the user """
user, user_profile = profile(request, raw=True)
data = {
"nickname": user.username,
"updated": user.created.isoformat(),
"published": user.created.isoformat(),
"profile": user_profile,
}
return json_response(data)
@oauth_required
@csrf_exempt
@user_has_privilege(u'uploader')
def uploads(request):
""" Endpoint for file uploads """
user = request.matchdict["username"]
requested_user = User.query.filter_by(username=user)
if requested_user is None:
error = "No such 'user' with id '{0}'".format(user)
return json_response({"error": error}, status=404)
request.user = requested_user[0]
if request.method == "POST":
# Wrap the data in the werkzeug file wrapper
mimetype = request.headers.get("Content-Type", "application/octal-stream")
filename = mimetypes.guess_all_extensions(mimetype)
filename = 'unknown' + filename[0] if filename else filename
file_data = FileStorage(
stream=io.BytesIO(request.data),
filename=filename,
content_type=mimetype
)
# Find media manager
media_type, media_manager = sniff_media(file_data, filename)
entry = new_upload_entry(request.user)
if hasattr(media_manager, "api_upload_request"):
return media_manager.api_upload_request(request, file_data, entry)
else:
return json_response({"error": "Not yet implemented"}, status=501)
return json_response({"error": "Not yet implemented"}, status=501)
@oauth_required
@csrf_exempt
def feed(request):
""" Handles the user's outbox - /api/user//feed """
user = request.matchdict["username"]
requested_user = User.query.filter_by(username=user)
# check if the user exists
if requested_user is None:
error = "No such 'user' with id '{0}'".format(user)
return json_response({"error": error}, status=404)
request.user = requested_user[0]
if request.data:
data = json.loads(request.data)
else:
data = {"verb": None, "object": {}}
if request.method == "POST" and data["verb"] == "post":
obj = data.get("object", None)
if obj is None:
error = {"error": "Could not find 'object' element."}
return json_response(error, status=400)
if obj.get("objectType", None) == "comment":
# post a comment
comment = MediaComment(author=request.user.id)
comment.unserialize(data["object"])
comment.save()
data = {"verb": "post", "object": comment.serialize(request)}
return json_response(data)
elif obj.get("objectType", None) == "image":
# Posting an image to the feed
media_id = int(data["object"]["id"])
media = MediaEntry.query.filter_by(id=media_id)
if media is None:
error = "No such 'image' with id '{0}'".format(id=media_id)
return json_response(error, status=404)
media = media.first()
if not media.unserialize(data["object"]):
error = {"error": "Invalid 'image' with id '{0}'".format(obj_id)}
return json_response(error, status=400)
media.save()
media.media_manager.api_add_to_feed(request, media)
return json_response({
"verb": "post",
"object": media.serialize(request)
})
elif obj.get("objectType", None) is None:
# They need to tell us what type of object they're giving us.
error = {"error": "No objectType specified."}
return json_response(error, status=400)
else:
# Oh no! We don't know about this type of object (yet)
error_message = "Unknown object type '{0}'.".format(
obj.get("objectType", None)
)
error = {"error": error_message}
return json_response(error, status=400)
elif request.method in ["PUT", "POST"] and data["verb"] == "update":
# Check we've got a valid object
obj = data.get("object", None)
if obj is None:
error = {"error": "Could not find 'object' element."}
return json_response(error, status=400)
if "objectType" not in obj:
error = {"error": "No objectType specified."}
return json_response(error, status=400)
if "id" not in obj:
error = {"error": "Object ID has not been specified."}
return json_response(error, status=400)
obj_id = obj["id"]
# Now try and find object
if obj["objectType"] == "comment":
comment = MediaComment.query.filter_by(id=obj_id)
if comment is None:
error = {"error": "No such 'comment' with id '{0}'.".format(obj_id)}
return json_response(error, status=400)
comment = comment[0]
if not comment.unserialize(data["object"]):
error = {"error": "Invalid 'comment' with id '{0}'".format(obj_id)}
return json_response(error, status=400)
comment.save()
activity = {
"verb": "update",
"object": comment.serialize(request),
}
return json_response(activity)
elif obj["objectType"] == "image":
image = MediaEntry.query.filter_by(id=obj_id)
if image is None:
error = {"error": "No such 'image' with the id '{0}'.".format(obj_id)}
return json_response(error, status=400)
image = image[0]
if not image.unserialize(obj):
error = {"error": "Invalid 'image' with id '{0}'".format(obj_id)}
return json_response(error, status=400)
image.save()
activity = {
"verb": "update",
"object": image.serialize(request),
}
return json_response(activity)
elif request.method != "GET":
# Currently unsupported
error = "Unsupported HTTP method {0}".format(request.method)
return json_response({"error": error}, status=501)
feed_url = request.urlgen(
"mediagoblin.federation.feed",
username=request.user.username,
qualified=True
)
feed = {
"displayName": "Activities by {user}@{host}".format(
user=request.user.username,
host=request.host
),
"objectTypes": ["activity"],
"url": feed_url,
"links": {
"first": {
"href": feed_url,
},
"self": {
"href": request.url,
},
"prev": {
"href": feed_url,
},
"next": {
"href": feed_url,
}
},
"author": request.user.serialize(request),
"items": [],
}
# Now lookup the user's feed.
for media in MediaEntry.query.all():
feed["items"].append({
"verb": "post",
"object": media.serialize(request),
"actor": request.user.serialize(request),
"content": "{0} posted a picture".format(request.user.username),
"id": 1,
})
feed["items"][-1]["updated"] = feed["items"][-1]["object"]["updated"]
feed["items"][-1]["published"] = feed["items"][-1]["object"]["published"]
feed["items"][-1]["url"] = feed["items"][-1]["object"]["url"]
feed["totalItems"] = len(feed["items"])
return json_response(feed)
@oauth_required
def object(request, raw_obj=False):
""" Lookup for a object type """
object_type = request.matchdict["objectType"]
slug = request.matchdict["slug"]
if object_type not in ["image"]:
error = "Unknown type: {0}".format(object_type)
# not sure why this is 404, maybe ask evan. Maybe 400?
return json_response({"error": error}, status=404)
media = MediaEntry.query.filter_by(slug=slug).first()
if media is None:
# no media found with that uuid
error = "Can't find a {0} with ID = {1}".format(object_type, slug)
return json_response({"error": error}, status=404)
if raw_obj:
return media
return json_response(media.serialize(request))
@oauth_required
def object_comments(request):
""" Looks up for the comments on a object """
media = object(request, raw_obj=True)
response = media
if isinstance(response, MediaEntry):
comments = response.serialize(request)
comments = comments.get("replies", {
"totalItems": 0,
"items": [],
"url": request.urlgen(
"mediagoblin.federation.object.comments",
objectType=media.objectType,
uuid=media.slug,
qualified=True
)
})
comments["displayName"] = "Replies to {0}".format(comments["url"])
comments["links"] = {
"first": comments["url"],
"self": comments["url"],
}
response = json_response(comments)
return response
##
# Well known
##
def host_meta(request):
""" This is /.well-known/host-meta - provides URL's to resources on server """
links = []
# Client registration links
links.append({
"ref": "registration_endpoint",
"href": request.urlgen("mediagoblin.oauth.client_register", qualified=True),
})
links.append({
"ref": "http://apinamespace.org/oauth/request_token",
"href": request.urlgen("mediagoblin.oauth.request_token", qualified=True),
})
links.append({
"ref": "http://apinamespace.org/oauth/authorize",
"href": request.urlgen("mediagoblin.oauth.authorize", qualified=True),
})
links.append({
"ref": "http://apinamespace.org/oauth/access_token",
"href": request.urlgen("mediagoblin.oauth.access_token", qualified=True),
})
return json_response({"links": links})
def whoami(request):
""" This is /api/whoami - This is a HTTP redirect to api profile """
profile = request.urlgen(
"mediagoblin.federation.user.profile",
username=request.user.username,
qualified=True
)
return redirect(request, location=profile)