2018-04-27 19:10:02 +00:00
|
|
|
import logging
|
2018-07-22 10:20:16 +00:00
|
|
|
import mimetypes
|
2018-03-31 13:47:21 +00:00
|
|
|
import urllib.parse
|
|
|
|
|
2018-07-22 10:20:16 +00:00
|
|
|
from django.core.exceptions import ObjectDoesNotExist
|
2018-04-06 15:58:43 +00:00
|
|
|
from django.core.paginator import Paginator
|
2018-03-31 13:47:21 +00:00
|
|
|
from rest_framework import serializers
|
2018-03-30 16:02:50 +00:00
|
|
|
|
2018-06-10 08:55:16 +00:00
|
|
|
from funkwhale_api.common import utils as funkwhale_utils
|
2018-05-23 19:50:23 +00:00
|
|
|
from funkwhale_api.music import models as music_models
|
2018-03-28 22:00:47 +00:00
|
|
|
|
2018-09-06 18:35:02 +00:00
|
|
|
from . import activity, models, utils
|
2018-03-28 22:00:47 +00:00
|
|
|
|
2018-04-03 21:24:51 +00:00
|
|
|
AP_CONTEXT = [
|
2018-06-09 13:36:16 +00:00
|
|
|
"https://www.w3.org/ns/activitystreams",
|
|
|
|
"https://w3id.org/security/v1",
|
2018-04-03 21:24:51 +00:00
|
|
|
{},
|
|
|
|
]
|
|
|
|
|
2018-04-27 19:10:02 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
2018-04-07 15:18:54 +00:00
|
|
|
|
2018-09-23 12:38:42 +00:00
|
|
|
class LinkSerializer(serializers.Serializer):
|
|
|
|
type = serializers.ChoiceField(choices=["Link"])
|
|
|
|
href = serializers.URLField(max_length=500)
|
|
|
|
mediaType = serializers.CharField()
|
|
|
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
self.allowed_mimetypes = kwargs.pop("allowed_mimetypes", [])
|
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
|
|
|
|
def validate_mediaType(self, v):
|
|
|
|
if not self.allowed_mimetypes:
|
|
|
|
# no restrictions
|
|
|
|
return v
|
|
|
|
for mt in self.allowed_mimetypes:
|
|
|
|
if mt.endswith("/*"):
|
|
|
|
if v.startswith(mt.replace("*", "")):
|
|
|
|
return v
|
|
|
|
else:
|
|
|
|
if v == mt:
|
|
|
|
return v
|
|
|
|
raise serializers.ValidationError(
|
|
|
|
"Invalid mimetype {}. Allowed: {}".format(v, self.allowed_mimetypes)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2018-04-12 17:30:39 +00:00
|
|
|
class ActorSerializer(serializers.Serializer):
|
2018-05-21 17:04:28 +00:00
|
|
|
id = serializers.URLField(max_length=500)
|
|
|
|
outbox = serializers.URLField(max_length=500)
|
|
|
|
inbox = serializers.URLField(max_length=500)
|
2018-04-12 17:30:39 +00:00
|
|
|
type = serializers.ChoiceField(choices=models.TYPE_CHOICES)
|
|
|
|
preferredUsername = serializers.CharField()
|
|
|
|
manuallyApprovesFollowers = serializers.NullBooleanField(required=False)
|
|
|
|
name = serializers.CharField(required=False, max_length=200)
|
2018-03-31 16:41:15 +00:00
|
|
|
summary = serializers.CharField(max_length=None, required=False)
|
2018-09-22 12:29:30 +00:00
|
|
|
followers = serializers.URLField(max_length=500)
|
2018-05-21 17:04:28 +00:00
|
|
|
following = serializers.URLField(max_length=500, required=False, allow_null=True)
|
2018-04-12 17:30:39 +00:00
|
|
|
publicKey = serializers.JSONField(required=False)
|
2018-03-30 16:02:50 +00:00
|
|
|
|
2018-03-31 13:47:21 +00:00
|
|
|
def to_representation(self, instance):
|
2018-04-12 17:30:39 +00:00
|
|
|
ret = {
|
2018-09-06 18:35:02 +00:00
|
|
|
"id": instance.fid,
|
2018-06-09 13:36:16 +00:00
|
|
|
"outbox": instance.outbox_url,
|
|
|
|
"inbox": instance.inbox_url,
|
|
|
|
"preferredUsername": instance.preferred_username,
|
|
|
|
"type": instance.type,
|
2018-04-12 17:30:39 +00:00
|
|
|
}
|
|
|
|
if instance.name:
|
2018-06-09 13:36:16 +00:00
|
|
|
ret["name"] = instance.name
|
2018-04-12 17:30:39 +00:00
|
|
|
if instance.followers_url:
|
2018-06-09 13:36:16 +00:00
|
|
|
ret["followers"] = instance.followers_url
|
2018-04-12 17:30:39 +00:00
|
|
|
if instance.following_url:
|
2018-06-09 13:36:16 +00:00
|
|
|
ret["following"] = instance.following_url
|
2018-04-12 17:30:39 +00:00
|
|
|
if instance.summary:
|
2018-06-09 13:36:16 +00:00
|
|
|
ret["summary"] = instance.summary
|
2018-04-12 17:30:39 +00:00
|
|
|
if instance.manually_approves_followers is not None:
|
2018-06-09 13:36:16 +00:00
|
|
|
ret["manuallyApprovesFollowers"] = instance.manually_approves_followers
|
2018-04-12 17:30:39 +00:00
|
|
|
|
2018-06-09 13:36:16 +00:00
|
|
|
ret["@context"] = AP_CONTEXT
|
2018-03-31 13:47:21 +00:00
|
|
|
if instance.public_key:
|
2018-06-09 13:36:16 +00:00
|
|
|
ret["publicKey"] = {
|
2018-09-06 18:35:02 +00:00
|
|
|
"owner": instance.fid,
|
2018-06-09 13:36:16 +00:00
|
|
|
"publicKeyPem": instance.public_key,
|
2018-09-06 18:35:02 +00:00
|
|
|
"id": "{}#main-key".format(instance.fid),
|
2018-03-31 13:47:21 +00:00
|
|
|
}
|
2018-06-09 13:36:16 +00:00
|
|
|
ret["endpoints"] = {}
|
2018-03-31 13:47:21 +00:00
|
|
|
if instance.shared_inbox_url:
|
2018-06-09 13:36:16 +00:00
|
|
|
ret["endpoints"]["sharedInbox"] = instance.shared_inbox_url
|
2018-07-22 10:20:16 +00:00
|
|
|
try:
|
|
|
|
if instance.user.avatar:
|
|
|
|
ret["icon"] = {
|
|
|
|
"type": "Image",
|
|
|
|
"mediaType": mimetypes.guess_type(instance.user.avatar.path)[0],
|
|
|
|
"url": utils.full_url(instance.user.avatar.crop["400x400"].url),
|
|
|
|
}
|
|
|
|
except ObjectDoesNotExist:
|
|
|
|
pass
|
2018-03-31 13:47:21 +00:00
|
|
|
return ret
|
|
|
|
|
|
|
|
def prepare_missing_fields(self):
|
2018-04-12 17:30:39 +00:00
|
|
|
kwargs = {
|
2018-09-06 18:35:02 +00:00
|
|
|
"fid": self.validated_data["id"],
|
2018-06-09 13:36:16 +00:00
|
|
|
"outbox_url": self.validated_data["outbox"],
|
|
|
|
"inbox_url": self.validated_data["inbox"],
|
|
|
|
"following_url": self.validated_data.get("following"),
|
|
|
|
"followers_url": self.validated_data.get("followers"),
|
|
|
|
"summary": self.validated_data.get("summary"),
|
|
|
|
"type": self.validated_data["type"],
|
|
|
|
"name": self.validated_data.get("name"),
|
|
|
|
"preferred_username": self.validated_data["preferredUsername"],
|
2018-04-12 17:30:39 +00:00
|
|
|
}
|
2018-06-09 13:36:16 +00:00
|
|
|
maf = self.validated_data.get("manuallyApprovesFollowers")
|
2018-04-12 17:30:39 +00:00
|
|
|
if maf is not None:
|
2018-06-09 13:36:16 +00:00
|
|
|
kwargs["manually_approves_followers"] = maf
|
2018-09-06 18:35:02 +00:00
|
|
|
domain = urllib.parse.urlparse(kwargs["fid"]).netloc
|
2018-12-27 19:39:03 +00:00
|
|
|
kwargs["domain"] = models.Domain.objects.get_or_create(pk=domain)[0]
|
2018-06-09 13:36:16 +00:00
|
|
|
for endpoint, url in self.initial_data.get("endpoints", {}).items():
|
|
|
|
if endpoint == "sharedInbox":
|
|
|
|
kwargs["shared_inbox_url"] = url
|
2018-03-31 13:47:21 +00:00
|
|
|
break
|
|
|
|
try:
|
2018-06-09 13:36:16 +00:00
|
|
|
kwargs["public_key"] = self.initial_data["publicKey"]["publicKeyPem"]
|
2018-03-31 13:47:21 +00:00
|
|
|
except KeyError:
|
|
|
|
pass
|
|
|
|
return kwargs
|
|
|
|
|
|
|
|
def build(self):
|
2018-04-12 17:30:39 +00:00
|
|
|
d = self.prepare_missing_fields()
|
|
|
|
return models.Actor(**d)
|
2018-03-31 13:47:21 +00:00
|
|
|
|
|
|
|
def save(self, **kwargs):
|
2018-04-12 17:30:39 +00:00
|
|
|
d = self.prepare_missing_fields()
|
|
|
|
d.update(kwargs)
|
2018-09-06 18:35:02 +00:00
|
|
|
return models.Actor.objects.update_or_create(fid=d["fid"], defaults=d)[0]
|
2018-03-31 13:47:21 +00:00
|
|
|
|
2018-03-31 16:41:15 +00:00
|
|
|
def validate_summary(self, value):
|
|
|
|
if value:
|
|
|
|
return value[:500]
|
|
|
|
|
|
|
|
|
2018-04-10 21:17:51 +00:00
|
|
|
class APIActorSerializer(serializers.ModelSerializer):
|
|
|
|
class Meta:
|
|
|
|
model = models.Actor
|
|
|
|
fields = [
|
2018-06-09 13:36:16 +00:00
|
|
|
"id",
|
2018-09-06 18:35:02 +00:00
|
|
|
"fid",
|
2018-06-09 13:36:16 +00:00
|
|
|
"url",
|
|
|
|
"creation_date",
|
|
|
|
"summary",
|
|
|
|
"preferred_username",
|
|
|
|
"name",
|
|
|
|
"last_fetch_date",
|
|
|
|
"domain",
|
|
|
|
"type",
|
|
|
|
"manually_approves_followers",
|
2018-09-06 18:35:02 +00:00
|
|
|
"full_username",
|
2018-04-10 21:17:51 +00:00
|
|
|
]
|
2018-04-11 19:58:41 +00:00
|
|
|
|
|
|
|
|
2018-09-06 18:35:02 +00:00
|
|
|
class BaseActivitySerializer(serializers.Serializer):
|
|
|
|
id = serializers.URLField(max_length=500, required=False)
|
|
|
|
type = serializers.CharField(max_length=100)
|
|
|
|
actor = serializers.URLField(max_length=500)
|
2018-06-09 13:36:16 +00:00
|
|
|
|
2018-09-06 18:35:02 +00:00
|
|
|
def validate_actor(self, v):
|
|
|
|
expected = self.context.get("actor")
|
|
|
|
if expected and expected.fid != v:
|
|
|
|
raise serializers.ValidationError("Invalid actor")
|
|
|
|
if expected:
|
|
|
|
# avoid a DB lookup
|
|
|
|
return expected
|
2018-04-14 16:50:37 +00:00
|
|
|
try:
|
2018-09-06 18:35:02 +00:00
|
|
|
return models.Actor.objects.get(fid=v)
|
|
|
|
except models.Actor.DoesNotExist:
|
|
|
|
raise serializers.ValidationError("Actor not found")
|
2018-04-14 16:50:37 +00:00
|
|
|
|
2018-09-06 18:35:02 +00:00
|
|
|
def create(self, validated_data):
|
|
|
|
return models.Activity.objects.create(
|
|
|
|
fid=validated_data.get("id"),
|
|
|
|
actor=validated_data["actor"],
|
|
|
|
payload=self.initial_data,
|
2018-09-13 15:18:23 +00:00
|
|
|
type=validated_data["type"],
|
2018-09-06 18:35:02 +00:00
|
|
|
)
|
2018-04-10 20:47:13 +00:00
|
|
|
|
2018-09-06 18:35:02 +00:00
|
|
|
def validate(self, data):
|
|
|
|
data["recipients"] = self.validate_recipients(self.initial_data)
|
|
|
|
return super().validate(data)
|
2018-04-10 20:47:13 +00:00
|
|
|
|
2018-09-06 18:35:02 +00:00
|
|
|
def validate_recipients(self, payload):
|
|
|
|
"""
|
|
|
|
Ensure we have at least a to/cc field with valid actors
|
|
|
|
"""
|
|
|
|
to = payload.get("to", [])
|
|
|
|
cc = payload.get("cc", [])
|
2018-04-10 20:47:13 +00:00
|
|
|
|
2018-09-06 18:35:02 +00:00
|
|
|
if not to and not cc:
|
|
|
|
raise serializers.ValidationError(
|
|
|
|
"We cannot handle an activity with no recipient"
|
2018-04-10 20:47:13 +00:00
|
|
|
)
|
|
|
|
|
2018-04-12 18:38:06 +00:00
|
|
|
|
2018-04-10 19:25:35 +00:00
|
|
|
class FollowSerializer(serializers.Serializer):
|
2018-05-21 17:04:28 +00:00
|
|
|
id = serializers.URLField(max_length=500)
|
|
|
|
object = serializers.URLField(max_length=500)
|
|
|
|
actor = serializers.URLField(max_length=500)
|
2018-06-09 13:36:16 +00:00
|
|
|
type = serializers.ChoiceField(choices=["Follow"])
|
2018-04-03 21:24:51 +00:00
|
|
|
|
2018-04-10 19:25:35 +00:00
|
|
|
def validate_object(self, v):
|
2018-06-09 13:36:16 +00:00
|
|
|
expected = self.context.get("follow_target")
|
2018-09-06 18:35:02 +00:00
|
|
|
if self.parent:
|
|
|
|
# it's probably an accept, so everything is inverted, the actor
|
|
|
|
# the recipient does not matter
|
|
|
|
recipient = None
|
|
|
|
else:
|
|
|
|
recipient = self.context.get("recipient")
|
|
|
|
if expected and expected.fid != v:
|
2018-06-09 13:36:16 +00:00
|
|
|
raise serializers.ValidationError("Invalid target")
|
2018-04-10 19:25:35 +00:00
|
|
|
try:
|
2018-09-06 18:35:02 +00:00
|
|
|
obj = models.Actor.objects.get(fid=v)
|
|
|
|
if recipient and recipient.fid != obj.fid:
|
|
|
|
raise serializers.ValidationError("Invalid target")
|
|
|
|
return obj
|
2018-04-10 19:25:35 +00:00
|
|
|
except models.Actor.DoesNotExist:
|
2018-09-06 18:35:02 +00:00
|
|
|
pass
|
|
|
|
try:
|
|
|
|
qs = music_models.Library.objects.filter(fid=v)
|
|
|
|
if recipient:
|
|
|
|
qs = qs.filter(actor=recipient)
|
|
|
|
return qs.get()
|
|
|
|
except music_models.Library.DoesNotExist:
|
|
|
|
pass
|
|
|
|
|
|
|
|
raise serializers.ValidationError("Target not found")
|
2018-04-10 19:25:35 +00:00
|
|
|
|
|
|
|
def validate_actor(self, v):
|
2018-06-09 13:36:16 +00:00
|
|
|
expected = self.context.get("follow_actor")
|
2018-09-06 18:35:02 +00:00
|
|
|
if expected and expected.fid != v:
|
2018-06-09 13:36:16 +00:00
|
|
|
raise serializers.ValidationError("Invalid actor")
|
2018-04-10 19:25:35 +00:00
|
|
|
try:
|
2018-09-06 18:35:02 +00:00
|
|
|
return models.Actor.objects.get(fid=v)
|
2018-04-10 19:25:35 +00:00
|
|
|
except models.Actor.DoesNotExist:
|
2018-06-09 13:36:16 +00:00
|
|
|
raise serializers.ValidationError("Actor not found")
|
2018-04-10 19:25:35 +00:00
|
|
|
|
|
|
|
def save(self, **kwargs):
|
2018-09-06 18:35:02 +00:00
|
|
|
target = self.validated_data["object"]
|
|
|
|
|
|
|
|
if target._meta.label == "music.Library":
|
|
|
|
follow_class = models.LibraryFollow
|
|
|
|
else:
|
|
|
|
follow_class = models.Follow
|
|
|
|
defaults = kwargs
|
|
|
|
defaults["fid"] = self.validated_data["id"]
|
|
|
|
return follow_class.objects.update_or_create(
|
2018-06-09 13:36:16 +00:00
|
|
|
actor=self.validated_data["actor"],
|
|
|
|
target=self.validated_data["object"],
|
2018-09-06 18:35:02 +00:00
|
|
|
defaults=defaults,
|
2018-04-10 19:25:35 +00:00
|
|
|
)[0]
|
2018-04-03 21:24:51 +00:00
|
|
|
|
|
|
|
def to_representation(self, instance):
|
2018-04-10 19:25:35 +00:00
|
|
|
return {
|
2018-06-09 13:36:16 +00:00
|
|
|
"@context": AP_CONTEXT,
|
2018-09-06 18:35:02 +00:00
|
|
|
"actor": instance.actor.fid,
|
|
|
|
"id": instance.get_federation_id(),
|
|
|
|
"object": instance.target.fid,
|
2018-06-09 13:36:16 +00:00
|
|
|
"type": "Follow",
|
2018-04-10 19:25:35 +00:00
|
|
|
}
|
2018-04-03 21:24:51 +00:00
|
|
|
|
|
|
|
|
2018-04-10 20:47:13 +00:00
|
|
|
class APIFollowSerializer(serializers.ModelSerializer):
|
2018-04-10 21:17:51 +00:00
|
|
|
actor = APIActorSerializer()
|
|
|
|
target = APIActorSerializer()
|
|
|
|
|
2018-04-10 20:47:13 +00:00
|
|
|
class Meta:
|
|
|
|
model = models.Follow
|
|
|
|
fields = [
|
2018-06-09 13:36:16 +00:00
|
|
|
"uuid",
|
|
|
|
"id",
|
|
|
|
"approved",
|
|
|
|
"creation_date",
|
|
|
|
"modification_date",
|
|
|
|
"actor",
|
|
|
|
"target",
|
2018-04-10 20:47:13 +00:00
|
|
|
]
|
|
|
|
|
|
|
|
|
2018-04-10 19:25:35 +00:00
|
|
|
class AcceptFollowSerializer(serializers.Serializer):
|
2018-09-06 18:35:02 +00:00
|
|
|
id = serializers.URLField(max_length=500, required=False)
|
2018-05-21 17:04:28 +00:00
|
|
|
actor = serializers.URLField(max_length=500)
|
2018-04-10 19:25:35 +00:00
|
|
|
object = FollowSerializer()
|
2018-06-09 13:36:16 +00:00
|
|
|
type = serializers.ChoiceField(choices=["Accept"])
|
2018-04-10 19:25:35 +00:00
|
|
|
|
|
|
|
def validate_actor(self, v):
|
2018-09-06 18:35:02 +00:00
|
|
|
expected = self.context.get("actor")
|
|
|
|
if expected and expected.fid != v:
|
2018-06-09 13:36:16 +00:00
|
|
|
raise serializers.ValidationError("Invalid actor")
|
2018-04-10 19:25:35 +00:00
|
|
|
try:
|
2018-09-06 18:35:02 +00:00
|
|
|
return models.Actor.objects.get(fid=v)
|
2018-04-10 19:25:35 +00:00
|
|
|
except models.Actor.DoesNotExist:
|
2018-06-09 13:36:16 +00:00
|
|
|
raise serializers.ValidationError("Actor not found")
|
2018-04-10 19:25:35 +00:00
|
|
|
|
|
|
|
def validate(self, validated_data):
|
2018-09-06 18:35:02 +00:00
|
|
|
# we ensure the accept actor actually match the follow target / library owner
|
|
|
|
target = validated_data["object"]["object"]
|
|
|
|
|
|
|
|
if target._meta.label == "music.Library":
|
|
|
|
expected = target.actor
|
|
|
|
follow_class = models.LibraryFollow
|
|
|
|
else:
|
|
|
|
expected = target
|
|
|
|
follow_class = models.Follow
|
|
|
|
if validated_data["actor"] != expected:
|
2018-06-09 13:36:16 +00:00
|
|
|
raise serializers.ValidationError("Actor mismatch")
|
2018-04-10 19:25:35 +00:00
|
|
|
try:
|
2018-06-09 13:36:16 +00:00
|
|
|
validated_data["follow"] = (
|
2018-09-06 18:35:02 +00:00
|
|
|
follow_class.objects.filter(
|
|
|
|
target=target, actor=validated_data["object"]["actor"]
|
2018-06-09 13:36:16 +00:00
|
|
|
)
|
|
|
|
.exclude(approved=True)
|
2018-09-06 18:35:02 +00:00
|
|
|
.select_related()
|
2018-06-09 13:36:16 +00:00
|
|
|
.get()
|
|
|
|
)
|
2018-09-06 18:35:02 +00:00
|
|
|
except follow_class.DoesNotExist:
|
2018-06-09 13:36:16 +00:00
|
|
|
raise serializers.ValidationError("No follow to accept")
|
2018-04-10 19:25:35 +00:00
|
|
|
return validated_data
|
|
|
|
|
|
|
|
def to_representation(self, instance):
|
2018-09-06 18:35:02 +00:00
|
|
|
if instance.target._meta.label == "music.Library":
|
|
|
|
actor = instance.target.actor
|
|
|
|
else:
|
|
|
|
actor = instance.target
|
|
|
|
|
2018-04-10 19:25:35 +00:00
|
|
|
return {
|
|
|
|
"@context": AP_CONTEXT,
|
2018-09-06 18:35:02 +00:00
|
|
|
"id": instance.get_federation_id() + "/accept",
|
2018-04-10 19:25:35 +00:00
|
|
|
"type": "Accept",
|
2018-09-06 18:35:02 +00:00
|
|
|
"actor": actor.fid,
|
2018-06-09 13:36:16 +00:00
|
|
|
"object": FollowSerializer(instance).data,
|
2018-04-10 19:25:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
def save(self):
|
2018-09-06 18:35:02 +00:00
|
|
|
follow = self.validated_data["follow"]
|
|
|
|
follow.approved = True
|
|
|
|
follow.save()
|
|
|
|
if follow.target._meta.label == "music.Library":
|
2018-09-24 18:44:22 +00:00
|
|
|
follow.target.schedule_scan(actor=follow.actor)
|
2018-09-06 18:35:02 +00:00
|
|
|
return follow
|
2018-04-10 19:25:35 +00:00
|
|
|
|
|
|
|
|
|
|
|
class UndoFollowSerializer(serializers.Serializer):
|
2018-05-21 17:04:28 +00:00
|
|
|
id = serializers.URLField(max_length=500)
|
|
|
|
actor = serializers.URLField(max_length=500)
|
2018-04-10 19:25:35 +00:00
|
|
|
object = FollowSerializer()
|
2018-06-09 13:36:16 +00:00
|
|
|
type = serializers.ChoiceField(choices=["Undo"])
|
2018-04-10 19:25:35 +00:00
|
|
|
|
|
|
|
def validate_actor(self, v):
|
2018-09-24 18:44:22 +00:00
|
|
|
expected = self.context.get("actor")
|
|
|
|
|
2018-09-06 18:35:02 +00:00
|
|
|
if expected and expected.fid != v:
|
2018-06-09 13:36:16 +00:00
|
|
|
raise serializers.ValidationError("Invalid actor")
|
2018-04-10 19:25:35 +00:00
|
|
|
try:
|
2018-09-06 18:35:02 +00:00
|
|
|
return models.Actor.objects.get(fid=v)
|
2018-04-10 19:25:35 +00:00
|
|
|
except models.Actor.DoesNotExist:
|
2018-06-09 13:36:16 +00:00
|
|
|
raise serializers.ValidationError("Actor not found")
|
2018-04-10 19:25:35 +00:00
|
|
|
|
|
|
|
def validate(self, validated_data):
|
|
|
|
# we ensure the accept actor actually match the follow actor
|
2018-06-09 13:36:16 +00:00
|
|
|
if validated_data["actor"] != validated_data["object"]["actor"]:
|
|
|
|
raise serializers.ValidationError("Actor mismatch")
|
2018-09-24 18:44:22 +00:00
|
|
|
|
|
|
|
target = validated_data["object"]["object"]
|
|
|
|
|
|
|
|
if target._meta.label == "music.Library":
|
|
|
|
follow_class = models.LibraryFollow
|
|
|
|
else:
|
|
|
|
follow_class = models.Follow
|
|
|
|
|
2018-04-10 19:25:35 +00:00
|
|
|
try:
|
2018-09-24 18:44:22 +00:00
|
|
|
validated_data["follow"] = follow_class.objects.filter(
|
|
|
|
actor=validated_data["actor"], target=target
|
2018-04-10 19:25:35 +00:00
|
|
|
).get()
|
2018-09-24 18:44:22 +00:00
|
|
|
except follow_class.DoesNotExist:
|
2018-06-09 13:36:16 +00:00
|
|
|
raise serializers.ValidationError("No follow to remove")
|
2018-04-10 19:25:35 +00:00
|
|
|
return validated_data
|
|
|
|
|
|
|
|
def to_representation(self, instance):
|
|
|
|
return {
|
|
|
|
"@context": AP_CONTEXT,
|
2018-09-06 18:35:02 +00:00
|
|
|
"id": instance.get_federation_id() + "/undo",
|
2018-04-10 19:25:35 +00:00
|
|
|
"type": "Undo",
|
2018-09-06 18:35:02 +00:00
|
|
|
"actor": instance.actor.fid,
|
2018-06-09 13:36:16 +00:00
|
|
|
"object": FollowSerializer(instance).data,
|
2018-04-10 19:25:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
def save(self):
|
2018-06-09 13:36:16 +00:00
|
|
|
return self.validated_data["follow"].delete()
|
2018-04-10 19:25:35 +00:00
|
|
|
|
|
|
|
|
2018-04-08 08:42:10 +00:00
|
|
|
class ActorWebfingerSerializer(serializers.Serializer):
|
|
|
|
subject = serializers.CharField()
|
2018-05-21 17:04:28 +00:00
|
|
|
aliases = serializers.ListField(child=serializers.URLField(max_length=500))
|
2018-04-08 08:42:10 +00:00
|
|
|
links = serializers.ListField()
|
2018-05-21 17:04:28 +00:00
|
|
|
actor_url = serializers.URLField(max_length=500, required=False)
|
2018-04-08 08:42:10 +00:00
|
|
|
|
|
|
|
def validate(self, validated_data):
|
2018-06-09 13:36:16 +00:00
|
|
|
validated_data["actor_url"] = None
|
|
|
|
for l in validated_data["links"]:
|
2018-04-08 08:42:10 +00:00
|
|
|
try:
|
2018-06-09 13:36:16 +00:00
|
|
|
if not l["rel"] == "self":
|
2018-04-08 08:42:10 +00:00
|
|
|
continue
|
2018-06-09 13:36:16 +00:00
|
|
|
if not l["type"] == "application/activity+json":
|
2018-04-08 08:42:10 +00:00
|
|
|
continue
|
2018-06-09 13:36:16 +00:00
|
|
|
validated_data["actor_url"] = l["href"]
|
2018-04-08 08:42:10 +00:00
|
|
|
break
|
|
|
|
except KeyError:
|
|
|
|
pass
|
2018-06-09 13:36:16 +00:00
|
|
|
if validated_data["actor_url"] is None:
|
|
|
|
raise serializers.ValidationError("No valid actor url found")
|
2018-04-08 08:42:10 +00:00
|
|
|
return validated_data
|
2018-03-31 13:47:21 +00:00
|
|
|
|
|
|
|
def to_representation(self, instance):
|
|
|
|
data = {}
|
2018-06-09 13:36:16 +00:00
|
|
|
data["subject"] = "acct:{}".format(instance.webfinger_subject)
|
|
|
|
data["links"] = [
|
2018-09-06 18:35:02 +00:00
|
|
|
{"rel": "self", "href": instance.fid, "type": "application/activity+json"}
|
2018-03-31 13:47:21 +00:00
|
|
|
]
|
2018-09-06 18:35:02 +00:00
|
|
|
data["aliases"] = [instance.fid]
|
2018-03-31 13:47:21 +00:00
|
|
|
return data
|
2018-04-01 20:11:46 +00:00
|
|
|
|
|
|
|
|
|
|
|
class ActivitySerializer(serializers.Serializer):
|
2018-05-21 17:04:28 +00:00
|
|
|
actor = serializers.URLField(max_length=500)
|
|
|
|
id = serializers.URLField(max_length=500, required=False)
|
2018-06-09 13:36:16 +00:00
|
|
|
type = serializers.ChoiceField(choices=[(c, c) for c in activity.ACTIVITY_TYPES])
|
2018-09-22 12:29:30 +00:00
|
|
|
object = serializers.JSONField(required=False)
|
|
|
|
target = serializers.JSONField(required=False)
|
2018-04-01 20:11:46 +00:00
|
|
|
|
|
|
|
def validate_object(self, value):
|
|
|
|
try:
|
2018-06-09 13:36:16 +00:00
|
|
|
type = value["type"]
|
2018-04-01 20:11:46 +00:00
|
|
|
except KeyError:
|
2018-06-09 13:36:16 +00:00
|
|
|
raise serializers.ValidationError("Missing object type")
|
2018-04-03 17:48:50 +00:00
|
|
|
except TypeError:
|
|
|
|
# probably a URL
|
|
|
|
return value
|
2018-04-01 20:11:46 +00:00
|
|
|
try:
|
|
|
|
object_serializer = OBJECT_SERIALIZERS[type]
|
|
|
|
except KeyError:
|
2018-06-09 13:36:16 +00:00
|
|
|
raise serializers.ValidationError("Unsupported type {}".format(type))
|
2018-04-01 20:11:46 +00:00
|
|
|
|
|
|
|
serializer = object_serializer(data=value)
|
|
|
|
serializer.is_valid(raise_exception=True)
|
|
|
|
return serializer.data
|
|
|
|
|
|
|
|
def validate_actor(self, value):
|
2018-06-09 13:36:16 +00:00
|
|
|
request_actor = self.context.get("actor")
|
2018-09-06 18:35:02 +00:00
|
|
|
if request_actor and request_actor.fid != value:
|
2018-04-01 20:11:46 +00:00
|
|
|
raise serializers.ValidationError(
|
2018-06-09 13:36:16 +00:00
|
|
|
"The actor making the request do not match" " the activity actor"
|
2018-04-01 20:11:46 +00:00
|
|
|
)
|
|
|
|
return value
|
|
|
|
|
2018-04-16 19:59:13 +00:00
|
|
|
def to_representation(self, conf):
|
|
|
|
d = {}
|
|
|
|
d.update(conf)
|
|
|
|
|
2018-06-09 13:36:16 +00:00
|
|
|
if self.context.get("include_ap_context", True):
|
|
|
|
d["@context"] = AP_CONTEXT
|
2018-04-16 19:59:13 +00:00
|
|
|
return d
|
|
|
|
|
2018-04-01 20:11:46 +00:00
|
|
|
|
|
|
|
class ObjectSerializer(serializers.Serializer):
|
2018-05-21 17:04:28 +00:00
|
|
|
id = serializers.URLField(max_length=500)
|
|
|
|
url = serializers.URLField(max_length=500, required=False, allow_null=True)
|
2018-06-09 13:36:16 +00:00
|
|
|
type = serializers.ChoiceField(choices=[(c, c) for c in activity.OBJECT_TYPES])
|
|
|
|
content = serializers.CharField(required=False, allow_null=True)
|
|
|
|
summary = serializers.CharField(required=False, allow_null=True)
|
|
|
|
name = serializers.CharField(required=False, allow_null=True)
|
|
|
|
published = serializers.DateTimeField(required=False, allow_null=True)
|
|
|
|
updated = serializers.DateTimeField(required=False, allow_null=True)
|
2018-04-01 20:11:46 +00:00
|
|
|
to = serializers.ListField(
|
2018-06-09 13:36:16 +00:00
|
|
|
child=serializers.URLField(max_length=500), required=False, allow_null=True
|
|
|
|
)
|
2018-04-01 20:11:46 +00:00
|
|
|
cc = serializers.ListField(
|
2018-06-09 13:36:16 +00:00
|
|
|
child=serializers.URLField(max_length=500), required=False, allow_null=True
|
|
|
|
)
|
2018-04-01 20:11:46 +00:00
|
|
|
bto = serializers.ListField(
|
2018-06-09 13:36:16 +00:00
|
|
|
child=serializers.URLField(max_length=500), required=False, allow_null=True
|
|
|
|
)
|
2018-04-01 20:11:46 +00:00
|
|
|
bcc = serializers.ListField(
|
2018-06-09 13:36:16 +00:00
|
|
|
child=serializers.URLField(max_length=500), required=False, allow_null=True
|
|
|
|
)
|
|
|
|
|
2018-04-01 20:11:46 +00:00
|
|
|
|
2018-06-09 13:36:16 +00:00
|
|
|
OBJECT_SERIALIZERS = {t: ObjectSerializer for t in activity.OBJECT_TYPES}
|
2018-04-06 15:58:43 +00:00
|
|
|
|
|
|
|
|
2018-09-06 18:35:02 +00:00
|
|
|
def get_additional_fields(data):
|
|
|
|
UNSET = object()
|
|
|
|
additional_fields = {}
|
|
|
|
for field in ["name", "summary"]:
|
|
|
|
v = data.get(field, UNSET)
|
|
|
|
if v == UNSET:
|
|
|
|
continue
|
|
|
|
additional_fields[field] = v
|
|
|
|
|
|
|
|
return additional_fields
|
|
|
|
|
|
|
|
|
2018-04-06 15:58:43 +00:00
|
|
|
class PaginatedCollectionSerializer(serializers.Serializer):
|
2018-06-09 13:36:16 +00:00
|
|
|
type = serializers.ChoiceField(choices=["Collection"])
|
2018-04-07 15:18:54 +00:00
|
|
|
totalItems = serializers.IntegerField(min_value=0)
|
2018-05-21 17:04:28 +00:00
|
|
|
actor = serializers.URLField(max_length=500)
|
|
|
|
id = serializers.URLField(max_length=500)
|
|
|
|
first = serializers.URLField(max_length=500)
|
|
|
|
last = serializers.URLField(max_length=500)
|
2018-04-06 15:58:43 +00:00
|
|
|
|
|
|
|
def to_representation(self, conf):
|
2018-06-09 13:36:16 +00:00
|
|
|
paginator = Paginator(conf["items"], conf.get("page_size", 20))
|
|
|
|
first = funkwhale_utils.set_query_parameter(conf["id"], page=1)
|
2018-04-06 15:58:43 +00:00
|
|
|
current = first
|
2018-06-09 13:36:16 +00:00
|
|
|
last = funkwhale_utils.set_query_parameter(conf["id"], page=paginator.num_pages)
|
2018-04-06 15:58:43 +00:00
|
|
|
d = {
|
2018-06-09 13:36:16 +00:00
|
|
|
"id": conf["id"],
|
2018-09-06 18:35:02 +00:00
|
|
|
"actor": conf["actor"].fid,
|
2018-06-09 13:36:16 +00:00
|
|
|
"totalItems": paginator.count,
|
2018-09-06 18:35:02 +00:00
|
|
|
"type": conf.get("type", "Collection"),
|
2018-06-09 13:36:16 +00:00
|
|
|
"current": current,
|
|
|
|
"first": first,
|
|
|
|
"last": last,
|
2018-04-06 15:58:43 +00:00
|
|
|
}
|
2018-09-06 18:35:02 +00:00
|
|
|
d.update(get_additional_fields(conf))
|
2018-06-09 13:36:16 +00:00
|
|
|
if self.context.get("include_ap_context", True):
|
|
|
|
d["@context"] = AP_CONTEXT
|
2018-04-06 15:58:43 +00:00
|
|
|
return d
|
|
|
|
|
|
|
|
|
2018-09-06 18:35:02 +00:00
|
|
|
class LibrarySerializer(PaginatedCollectionSerializer):
|
|
|
|
type = serializers.ChoiceField(choices=["Library"])
|
|
|
|
name = serializers.CharField()
|
|
|
|
summary = serializers.CharField(allow_blank=True, allow_null=True, required=False)
|
2018-09-22 12:29:30 +00:00
|
|
|
followers = serializers.URLField(max_length=500)
|
2018-09-06 18:35:02 +00:00
|
|
|
audience = serializers.ChoiceField(
|
|
|
|
choices=["", None, "https://www.w3.org/ns/activitystreams#Public"],
|
|
|
|
required=False,
|
|
|
|
allow_null=True,
|
|
|
|
allow_blank=True,
|
|
|
|
)
|
|
|
|
|
|
|
|
def to_representation(self, library):
|
|
|
|
conf = {
|
|
|
|
"id": library.fid,
|
|
|
|
"name": library.name,
|
|
|
|
"summary": library.description,
|
|
|
|
"page_size": 100,
|
|
|
|
"actor": library.actor,
|
2018-09-24 18:44:22 +00:00
|
|
|
"items": library.uploads.for_federation(),
|
2018-09-06 18:35:02 +00:00
|
|
|
"type": "Library",
|
|
|
|
}
|
|
|
|
r = super().to_representation(conf)
|
|
|
|
r["audience"] = (
|
|
|
|
"https://www.w3.org/ns/activitystreams#Public"
|
2018-11-09 18:51:47 +00:00
|
|
|
if library.privacy_level == "everyone"
|
2018-09-06 18:35:02 +00:00
|
|
|
else ""
|
|
|
|
)
|
2018-09-22 12:29:30 +00:00
|
|
|
r["followers"] = library.followers_url
|
2018-09-06 18:35:02 +00:00
|
|
|
return r
|
|
|
|
|
|
|
|
def create(self, validated_data):
|
2019-01-09 16:52:14 +00:00
|
|
|
actor = utils.retrieve_ap_object(
|
2018-09-06 18:35:02 +00:00
|
|
|
validated_data["actor"],
|
|
|
|
queryset=models.Actor,
|
|
|
|
serializer_class=ActorSerializer,
|
|
|
|
)
|
|
|
|
library, created = music_models.Library.objects.update_or_create(
|
|
|
|
fid=validated_data["id"],
|
|
|
|
actor=actor,
|
|
|
|
defaults={
|
2018-09-22 12:29:30 +00:00
|
|
|
"uploads_count": validated_data["totalItems"],
|
2018-09-06 18:35:02 +00:00
|
|
|
"name": validated_data["name"],
|
|
|
|
"description": validated_data["summary"],
|
2018-09-22 12:29:30 +00:00
|
|
|
"followers_url": validated_data["followers"],
|
2018-09-06 18:35:02 +00:00
|
|
|
"privacy_level": "everyone"
|
|
|
|
if validated_data["audience"]
|
|
|
|
== "https://www.w3.org/ns/activitystreams#Public"
|
|
|
|
else "me",
|
|
|
|
},
|
|
|
|
)
|
|
|
|
return library
|
|
|
|
|
|
|
|
|
2018-04-06 15:58:43 +00:00
|
|
|
class CollectionPageSerializer(serializers.Serializer):
|
2018-06-09 13:36:16 +00:00
|
|
|
type = serializers.ChoiceField(choices=["CollectionPage"])
|
2018-04-07 15:18:54 +00:00
|
|
|
totalItems = serializers.IntegerField(min_value=0)
|
|
|
|
items = serializers.ListField()
|
2018-05-21 17:04:28 +00:00
|
|
|
actor = serializers.URLField(max_length=500)
|
|
|
|
id = serializers.URLField(max_length=500)
|
|
|
|
first = serializers.URLField(max_length=500)
|
|
|
|
last = serializers.URLField(max_length=500)
|
|
|
|
next = serializers.URLField(max_length=500, required=False)
|
|
|
|
prev = serializers.URLField(max_length=500, required=False)
|
|
|
|
partOf = serializers.URLField(max_length=500)
|
2018-04-06 15:58:43 +00:00
|
|
|
|
2018-04-11 21:13:33 +00:00
|
|
|
def validate_items(self, v):
|
2018-06-09 13:36:16 +00:00
|
|
|
item_serializer = self.context.get("item_serializer")
|
2018-04-11 21:13:33 +00:00
|
|
|
if not item_serializer:
|
|
|
|
return v
|
|
|
|
raw_items = [item_serializer(data=i, context=self.context) for i in v]
|
2018-04-17 20:58:43 +00:00
|
|
|
valid_items = []
|
2018-04-11 21:13:33 +00:00
|
|
|
for i in raw_items:
|
2018-09-24 18:44:22 +00:00
|
|
|
try:
|
|
|
|
i.is_valid(raise_exception=True)
|
2018-04-17 20:58:43 +00:00
|
|
|
valid_items.append(i)
|
2018-09-24 18:44:22 +00:00
|
|
|
except serializers.ValidationError:
|
2018-06-09 13:36:16 +00:00
|
|
|
logger.debug("Invalid item %s: %s", i.data, i.errors)
|
2018-04-11 21:13:33 +00:00
|
|
|
|
2018-04-17 20:58:43 +00:00
|
|
|
return valid_items
|
2018-04-11 21:13:33 +00:00
|
|
|
|
2018-04-06 15:58:43 +00:00
|
|
|
def to_representation(self, conf):
|
2018-06-09 13:36:16 +00:00
|
|
|
page = conf["page"]
|
|
|
|
first = funkwhale_utils.set_query_parameter(conf["id"], page=1)
|
2018-04-10 20:47:13 +00:00
|
|
|
last = funkwhale_utils.set_query_parameter(
|
2018-06-09 13:36:16 +00:00
|
|
|
conf["id"], page=page.paginator.num_pages
|
|
|
|
)
|
|
|
|
id = funkwhale_utils.set_query_parameter(conf["id"], page=page.number)
|
2018-04-06 15:58:43 +00:00
|
|
|
d = {
|
2018-06-09 13:36:16 +00:00
|
|
|
"id": id,
|
|
|
|
"partOf": conf["id"],
|
2018-09-06 18:35:02 +00:00
|
|
|
"actor": conf["actor"].fid,
|
2018-06-09 13:36:16 +00:00
|
|
|
"totalItems": page.paginator.count,
|
|
|
|
"type": "CollectionPage",
|
|
|
|
"first": first,
|
|
|
|
"last": last,
|
|
|
|
"items": [
|
|
|
|
conf["item_serializer"](
|
|
|
|
i, context={"actor": conf["actor"], "include_ap_context": False}
|
2018-04-06 15:58:43 +00:00
|
|
|
).data
|
|
|
|
for i in page.object_list
|
2018-06-09 13:36:16 +00:00
|
|
|
],
|
2018-04-06 15:58:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if page.has_previous():
|
2018-06-09 13:36:16 +00:00
|
|
|
d["prev"] = funkwhale_utils.set_query_parameter(
|
|
|
|
conf["id"], page=page.previous_page_number()
|
|
|
|
)
|
2018-04-06 15:58:43 +00:00
|
|
|
|
2018-04-07 16:37:40 +00:00
|
|
|
if page.has_next():
|
2018-06-09 13:36:16 +00:00
|
|
|
d["next"] = funkwhale_utils.set_query_parameter(
|
|
|
|
conf["id"], page=page.next_page_number()
|
|
|
|
)
|
2018-09-06 18:35:02 +00:00
|
|
|
d.update(get_additional_fields(conf))
|
2018-06-09 13:36:16 +00:00
|
|
|
if self.context.get("include_ap_context", True):
|
|
|
|
d["@context"] = AP_CONTEXT
|
2018-04-06 15:58:43 +00:00
|
|
|
return d
|
2018-04-07 09:29:40 +00:00
|
|
|
|
|
|
|
|
2018-09-22 12:29:30 +00:00
|
|
|
class MusicEntitySerializer(serializers.Serializer):
|
|
|
|
id = serializers.URLField(max_length=500)
|
|
|
|
published = serializers.DateTimeField()
|
|
|
|
musicbrainzId = serializers.UUIDField(allow_null=True, required=False)
|
|
|
|
name = serializers.CharField(max_length=1000)
|
|
|
|
|
|
|
|
|
|
|
|
class ArtistSerializer(MusicEntitySerializer):
|
|
|
|
def to_representation(self, instance):
|
|
|
|
d = {
|
|
|
|
"type": "Artist",
|
|
|
|
"id": instance.fid,
|
|
|
|
"name": instance.name,
|
|
|
|
"published": instance.creation_date.isoformat(),
|
|
|
|
"musicbrainzId": str(instance.mbid) if instance.mbid else None,
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.context.get("include_ap_context", self.parent is None):
|
|
|
|
d["@context"] = AP_CONTEXT
|
|
|
|
return d
|
|
|
|
|
|
|
|
|
|
|
|
class AlbumSerializer(MusicEntitySerializer):
|
|
|
|
released = serializers.DateField(allow_null=True, required=False)
|
|
|
|
artists = serializers.ListField(child=ArtistSerializer(), min_length=1)
|
2018-09-23 12:38:42 +00:00
|
|
|
cover = LinkSerializer(
|
|
|
|
allowed_mimetypes=["image/*"], allow_null=True, required=False
|
|
|
|
)
|
2018-09-22 12:29:30 +00:00
|
|
|
|
|
|
|
def to_representation(self, instance):
|
|
|
|
d = {
|
|
|
|
"type": "Album",
|
|
|
|
"id": instance.fid,
|
|
|
|
"name": instance.title,
|
|
|
|
"published": instance.creation_date.isoformat(),
|
|
|
|
"musicbrainzId": str(instance.mbid) if instance.mbid else None,
|
|
|
|
"released": instance.release_date.isoformat()
|
|
|
|
if instance.release_date
|
|
|
|
else None,
|
|
|
|
"artists": [
|
|
|
|
ArtistSerializer(
|
|
|
|
instance.artist, context={"include_ap_context": False}
|
|
|
|
).data
|
|
|
|
],
|
|
|
|
}
|
|
|
|
if instance.cover:
|
2018-09-23 12:38:42 +00:00
|
|
|
d["cover"] = {
|
|
|
|
"type": "Link",
|
|
|
|
"href": utils.full_url(instance.cover.url),
|
|
|
|
"mediaType": mimetypes.guess_type(instance.cover.path)[0]
|
|
|
|
or "image/jpeg",
|
|
|
|
}
|
2018-09-22 12:29:30 +00:00
|
|
|
if self.context.get("include_ap_context", self.parent is None):
|
|
|
|
d["@context"] = AP_CONTEXT
|
|
|
|
return d
|
2018-04-07 09:29:40 +00:00
|
|
|
|
2018-09-22 12:29:30 +00:00
|
|
|
def get_create_data(self, validated_data):
|
|
|
|
artist_data = validated_data["artists"][0]
|
|
|
|
artist = ArtistSerializer(
|
|
|
|
context={"activity": self.context.get("activity")}
|
|
|
|
).create(artist_data)
|
|
|
|
|
|
|
|
return {
|
|
|
|
"mbid": validated_data.get("musicbrainzId"),
|
|
|
|
"fid": validated_data["id"],
|
|
|
|
"title": validated_data["name"],
|
|
|
|
"creation_date": validated_data["published"],
|
|
|
|
"artist": artist,
|
|
|
|
"release_date": validated_data.get("released"),
|
|
|
|
"from_activity": self.context.get("activity"),
|
|
|
|
}
|
2018-04-07 09:29:40 +00:00
|
|
|
|
|
|
|
|
2018-09-22 12:29:30 +00:00
|
|
|
class TrackSerializer(MusicEntitySerializer):
|
|
|
|
position = serializers.IntegerField(min_value=0, allow_null=True, required=False)
|
2018-12-06 08:53:31 +00:00
|
|
|
disc = serializers.IntegerField(min_value=1, allow_null=True, required=False)
|
2018-09-22 12:29:30 +00:00
|
|
|
artists = serializers.ListField(child=ArtistSerializer(), min_length=1)
|
|
|
|
album = AlbumSerializer()
|
2018-12-04 14:13:37 +00:00
|
|
|
license = serializers.URLField(allow_null=True, required=False)
|
|
|
|
copyright = serializers.CharField(allow_null=True, required=False)
|
2018-09-22 12:29:30 +00:00
|
|
|
|
|
|
|
def to_representation(self, instance):
|
|
|
|
d = {
|
|
|
|
"type": "Track",
|
|
|
|
"id": instance.fid,
|
|
|
|
"name": instance.title,
|
|
|
|
"published": instance.creation_date.isoformat(),
|
|
|
|
"musicbrainzId": str(instance.mbid) if instance.mbid else None,
|
|
|
|
"position": instance.position,
|
2018-12-06 08:53:31 +00:00
|
|
|
"disc": instance.disc_number,
|
2018-12-04 14:13:37 +00:00
|
|
|
"license": instance.local_license["identifiers"][0]
|
|
|
|
if instance.local_license
|
|
|
|
else None,
|
|
|
|
"copyright": instance.copyright if instance.copyright else None,
|
2018-09-22 12:29:30 +00:00
|
|
|
"artists": [
|
|
|
|
ArtistSerializer(
|
|
|
|
instance.artist, context={"include_ap_context": False}
|
|
|
|
).data
|
|
|
|
],
|
|
|
|
"album": AlbumSerializer(
|
|
|
|
instance.album, context={"include_ap_context": False}
|
|
|
|
).data,
|
|
|
|
}
|
2018-04-07 09:29:40 +00:00
|
|
|
|
2018-09-22 12:29:30 +00:00
|
|
|
if self.context.get("include_ap_context", self.parent is None):
|
|
|
|
d["@context"] = AP_CONTEXT
|
|
|
|
return d
|
2018-04-07 09:29:40 +00:00
|
|
|
|
2018-09-23 12:38:42 +00:00
|
|
|
def create(self, validated_data):
|
|
|
|
from funkwhale_api.music import tasks as music_tasks
|
2018-04-07 09:29:40 +00:00
|
|
|
|
2018-09-23 12:38:42 +00:00
|
|
|
metadata = music_tasks.federation_audio_track_to_metadata(validated_data)
|
|
|
|
from_activity = self.context.get("activity")
|
|
|
|
if from_activity:
|
|
|
|
metadata["from_activity_id"] = from_activity.pk
|
|
|
|
track = music_tasks.get_track_from_import_metadata(metadata)
|
|
|
|
return track
|
2018-04-07 09:29:40 +00:00
|
|
|
|
|
|
|
|
2018-09-22 12:29:30 +00:00
|
|
|
class UploadSerializer(serializers.Serializer):
|
|
|
|
type = serializers.ChoiceField(choices=["Audio"])
|
2018-05-21 17:04:28 +00:00
|
|
|
id = serializers.URLField(max_length=500)
|
2018-09-06 18:35:02 +00:00
|
|
|
library = serializers.URLField(max_length=500)
|
2018-09-23 12:38:42 +00:00
|
|
|
url = LinkSerializer(allowed_mimetypes=["audio/*"])
|
2018-04-07 09:29:40 +00:00
|
|
|
published = serializers.DateTimeField()
|
2018-09-22 12:29:30 +00:00
|
|
|
updated = serializers.DateTimeField(required=False, allow_null=True)
|
|
|
|
bitrate = serializers.IntegerField(min_value=0)
|
|
|
|
size = serializers.IntegerField(min_value=0)
|
|
|
|
duration = serializers.IntegerField(min_value=0)
|
2018-04-07 09:29:40 +00:00
|
|
|
|
2018-09-22 12:29:30 +00:00
|
|
|
track = TrackSerializer(required=True)
|
2018-04-07 09:29:40 +00:00
|
|
|
|
|
|
|
def validate_url(self, v):
|
|
|
|
try:
|
2018-06-09 15:41:59 +00:00
|
|
|
v["href"]
|
2018-04-07 09:29:40 +00:00
|
|
|
except (KeyError, TypeError):
|
2018-06-09 13:36:16 +00:00
|
|
|
raise serializers.ValidationError("Missing href")
|
2018-04-07 09:29:40 +00:00
|
|
|
|
|
|
|
try:
|
2018-06-09 13:36:16 +00:00
|
|
|
media_type = v["mediaType"]
|
2018-04-07 09:29:40 +00:00
|
|
|
except (KeyError, TypeError):
|
2018-06-09 13:36:16 +00:00
|
|
|
raise serializers.ValidationError("Missing mediaType")
|
2018-04-07 09:29:40 +00:00
|
|
|
|
2018-06-09 13:36:16 +00:00
|
|
|
if not media_type or not media_type.startswith("audio/"):
|
|
|
|
raise serializers.ValidationError("Invalid mediaType")
|
2018-04-07 09:29:40 +00:00
|
|
|
|
|
|
|
return v
|
|
|
|
|
2018-09-06 18:35:02 +00:00
|
|
|
def validate_library(self, v):
|
|
|
|
lb = self.context.get("library")
|
|
|
|
if lb:
|
|
|
|
if lb.fid != v:
|
|
|
|
raise serializers.ValidationError("Invalid library")
|
|
|
|
return lb
|
2018-09-22 12:29:30 +00:00
|
|
|
|
|
|
|
actor = self.context.get("actor")
|
|
|
|
kwargs = {}
|
|
|
|
if actor:
|
|
|
|
kwargs["actor"] = actor
|
2018-09-06 18:35:02 +00:00
|
|
|
try:
|
2018-09-22 12:29:30 +00:00
|
|
|
return music_models.Library.objects.get(fid=v, **kwargs)
|
2018-09-06 18:35:02 +00:00
|
|
|
except music_models.Library.DoesNotExist:
|
|
|
|
raise serializers.ValidationError("Invalid library")
|
|
|
|
|
2018-04-07 09:29:40 +00:00
|
|
|
def create(self, validated_data):
|
2018-09-22 12:29:30 +00:00
|
|
|
try:
|
|
|
|
return music_models.Upload.objects.get(fid=validated_data["id"])
|
|
|
|
except music_models.Upload.DoesNotExist:
|
|
|
|
pass
|
|
|
|
|
|
|
|
track = TrackSerializer(
|
|
|
|
context={"activity": self.context.get("activity")}
|
|
|
|
).create(validated_data["track"])
|
|
|
|
|
|
|
|
data = {
|
|
|
|
"fid": validated_data["id"],
|
2018-09-06 18:35:02 +00:00
|
|
|
"mimetype": validated_data["url"]["mediaType"],
|
|
|
|
"source": validated_data["url"]["href"],
|
|
|
|
"creation_date": validated_data["published"],
|
2018-06-09 13:36:16 +00:00
|
|
|
"modification_date": validated_data.get("updated"),
|
2018-09-22 12:29:30 +00:00
|
|
|
"track": track,
|
|
|
|
"duration": validated_data["duration"],
|
|
|
|
"size": validated_data["size"],
|
|
|
|
"bitrate": validated_data["bitrate"],
|
|
|
|
"library": validated_data["library"],
|
|
|
|
"from_activity": self.context.get("activity"),
|
|
|
|
"import_status": "finished",
|
2018-04-07 09:29:40 +00:00
|
|
|
}
|
2018-09-22 12:29:30 +00:00
|
|
|
return music_models.Upload.objects.create(**data)
|
2018-04-07 09:29:40 +00:00
|
|
|
|
|
|
|
def to_representation(self, instance):
|
|
|
|
track = instance.track
|
|
|
|
d = {
|
2018-06-09 13:36:16 +00:00
|
|
|
"type": "Audio",
|
2018-09-06 18:35:02 +00:00
|
|
|
"id": instance.get_federation_id(),
|
2018-09-22 12:29:30 +00:00
|
|
|
"library": instance.library.fid,
|
|
|
|
"name": track.full_name,
|
2018-06-09 13:36:16 +00:00
|
|
|
"published": instance.creation_date.isoformat(),
|
2018-09-22 12:29:30 +00:00
|
|
|
"bitrate": instance.bitrate,
|
|
|
|
"size": instance.size,
|
|
|
|
"duration": instance.duration,
|
2018-06-09 13:36:16 +00:00
|
|
|
"url": {
|
2018-09-06 18:35:02 +00:00
|
|
|
"href": utils.full_url(instance.listen_url),
|
2018-06-09 13:36:16 +00:00
|
|
|
"type": "Link",
|
|
|
|
"mediaType": instance.mimetype,
|
2018-04-07 09:29:40 +00:00
|
|
|
},
|
2018-09-22 12:29:30 +00:00
|
|
|
"track": TrackSerializer(track, context={"include_ap_context": False}).data,
|
2018-04-07 09:29:40 +00:00
|
|
|
}
|
2018-09-06 18:35:02 +00:00
|
|
|
if instance.modification_date:
|
|
|
|
d["updated"] = instance.modification_date.isoformat()
|
|
|
|
|
2018-09-22 12:29:30 +00:00
|
|
|
if self.context.get("include_ap_context", self.parent is None):
|
2018-06-09 13:36:16 +00:00
|
|
|
d["@context"] = AP_CONTEXT
|
2018-04-07 09:29:40 +00:00
|
|
|
return d
|
|
|
|
|
|
|
|
|
|
|
|
class CollectionSerializer(serializers.Serializer):
|
|
|
|
def to_representation(self, conf):
|
|
|
|
d = {
|
2018-06-09 13:36:16 +00:00
|
|
|
"id": conf["id"],
|
2018-09-06 18:35:02 +00:00
|
|
|
"actor": conf["actor"].fid,
|
2018-06-09 13:36:16 +00:00
|
|
|
"totalItems": len(conf["items"]),
|
|
|
|
"type": "Collection",
|
|
|
|
"items": [
|
|
|
|
conf["item_serializer"](
|
|
|
|
i, context={"actor": conf["actor"], "include_ap_context": False}
|
2018-04-07 09:29:40 +00:00
|
|
|
).data
|
2018-06-09 13:36:16 +00:00
|
|
|
for i in conf["items"]
|
|
|
|
],
|
2018-04-07 09:29:40 +00:00
|
|
|
}
|
|
|
|
|
2018-06-09 13:36:16 +00:00
|
|
|
if self.context.get("include_ap_context", True):
|
|
|
|
d["@context"] = AP_CONTEXT
|
2018-04-07 09:29:40 +00:00
|
|
|
return d
|
2018-12-27 16:42:43 +00:00
|
|
|
|
|
|
|
|
|
|
|
class NodeInfoLinkSerializer(serializers.Serializer):
|
|
|
|
href = serializers.URLField()
|
|
|
|
rel = serializers.URLField()
|
|
|
|
|
|
|
|
|
|
|
|
class NodeInfoSerializer(serializers.Serializer):
|
2018-12-27 19:39:03 +00:00
|
|
|
links = serializers.ListField(child=NodeInfoLinkSerializer(), min_length=1)
|