278 lines
8.9 KiB
Python
278 lines
8.9 KiB
Python
import datetime
|
|
|
|
from django.conf import settings
|
|
from django.core import validators
|
|
from django.core.exceptions import ObjectDoesNotExist
|
|
from django.utils import timezone
|
|
from drf_spectacular.types import OpenApiTypes
|
|
from drf_spectacular.utils import extend_schema_field
|
|
from rest_framework import serializers
|
|
|
|
from funkwhale_api.audio import models as audio_models
|
|
from funkwhale_api.common import fields as common_fields
|
|
from funkwhale_api.common import serializers as common_serializers
|
|
from funkwhale_api.music import models as music_models
|
|
from funkwhale_api.users import serializers as users_serializers
|
|
|
|
from . import filters, models
|
|
from . import serializers as federation_serializers
|
|
|
|
|
|
class NestedLibraryFollowSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = models.LibraryFollow
|
|
fields = ["creation_date", "uuid", "fid", "approved", "modification_date"]
|
|
|
|
|
|
class LibraryScanSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = music_models.LibraryScan
|
|
fields = [
|
|
"total_files",
|
|
"processed_files",
|
|
"errored_files",
|
|
"status",
|
|
"creation_date",
|
|
"modification_date",
|
|
]
|
|
|
|
|
|
class DomainSerializer(serializers.Serializer):
|
|
name = serializers.CharField()
|
|
|
|
|
|
class LibrarySerializer(serializers.ModelSerializer):
|
|
actor = federation_serializers.APIActorSerializer()
|
|
uploads_count = serializers.SerializerMethodField()
|
|
latest_scan = LibraryScanSerializer(required=False, allow_null=True)
|
|
# The follow field is likely broken, so I removed the test
|
|
follow = NestedLibraryFollowSerializer(required=False, allow_null=True)
|
|
|
|
class Meta:
|
|
model = music_models.Library
|
|
fields = [
|
|
"fid",
|
|
"uuid",
|
|
"actor",
|
|
"name",
|
|
"description",
|
|
"creation_date",
|
|
"uploads_count",
|
|
"privacy_level",
|
|
"follow",
|
|
"latest_scan",
|
|
]
|
|
|
|
def get_uploads_count(self, o) -> int:
|
|
return max(getattr(o, "_uploads_count", 0), o.uploads_count)
|
|
|
|
@extend_schema_field(NestedLibraryFollowSerializer)
|
|
def get_follow(self, o):
|
|
try:
|
|
return NestedLibraryFollowSerializer(o._follows[0]).data
|
|
except (AttributeError, IndexError):
|
|
return None
|
|
|
|
|
|
class LibraryFollowSerializer(serializers.ModelSerializer):
|
|
target = common_serializers.RelatedField("uuid", LibrarySerializer(), required=True)
|
|
actor = serializers.SerializerMethodField()
|
|
|
|
class Meta:
|
|
model = models.LibraryFollow
|
|
fields = ["creation_date", "actor", "uuid", "target", "approved"]
|
|
read_only_fields = ["uuid", "actor", "approved", "creation_date"]
|
|
|
|
def validate_target(self, v):
|
|
actor = self.context["actor"]
|
|
if v.actor == actor:
|
|
raise serializers.ValidationError("You cannot follow your own library")
|
|
|
|
if v.received_follows.filter(actor=actor).exists():
|
|
raise serializers.ValidationError("You are already following this library")
|
|
return v
|
|
|
|
@extend_schema_field(federation_serializers.APIActorSerializer)
|
|
def get_actor(self, o):
|
|
return federation_serializers.APIActorSerializer(o.actor).data
|
|
|
|
|
|
def serialize_generic_relation(activity, obj):
|
|
data = {"type": obj._meta.label}
|
|
if data["type"] == "federation.Actor":
|
|
data["full_username"] = obj.full_username
|
|
else:
|
|
data["uuid"] = obj.uuid
|
|
|
|
if data["type"] == "music.Library":
|
|
data["name"] = obj.name
|
|
if data["type"] == "federation.LibraryFollow":
|
|
data["approved"] = obj.approved
|
|
|
|
return data
|
|
|
|
|
|
class ActivitySerializer(serializers.ModelSerializer):
|
|
actor = federation_serializers.APIActorSerializer()
|
|
object = serializers.SerializerMethodField(allow_null=True)
|
|
target = serializers.SerializerMethodField(allow_null=True)
|
|
related_object = serializers.SerializerMethodField()
|
|
|
|
class Meta:
|
|
model = models.Activity
|
|
fields = [
|
|
"uuid",
|
|
"fid",
|
|
"actor",
|
|
"payload",
|
|
"object",
|
|
"target",
|
|
"related_object",
|
|
"actor",
|
|
"creation_date",
|
|
"type",
|
|
]
|
|
|
|
@extend_schema_field(OpenApiTypes.OBJECT, None)
|
|
def get_object(self, o):
|
|
if o.object:
|
|
return serialize_generic_relation(o, o.object)
|
|
|
|
@extend_schema_field(OpenApiTypes.OBJECT)
|
|
def get_related_object(self, o):
|
|
if o.related_object:
|
|
return serialize_generic_relation(o, o.related_object)
|
|
|
|
@extend_schema_field(OpenApiTypes.OBJECT)
|
|
def get_target(self, o):
|
|
if o.target:
|
|
return serialize_generic_relation(o, o.target)
|
|
|
|
|
|
class InboxItemSerializer(serializers.ModelSerializer):
|
|
activity = ActivitySerializer()
|
|
|
|
class Meta:
|
|
model = models.InboxItem
|
|
fields = ["id", "type", "activity", "is_read"]
|
|
read_only_fields = ["id", "type", "activity"]
|
|
|
|
|
|
class InboxItemActionSerializer(common_serializers.ActionSerializer):
|
|
actions = [common_serializers.Action("read", allow_all=True)]
|
|
filterset_class = filters.InboxItemFilter
|
|
|
|
def handle_read(self, objects):
|
|
return objects.update(is_read=True)
|
|
|
|
|
|
FETCH_OBJECT_CONFIG = {
|
|
"artist": {"queryset": music_models.Artist.objects.all()},
|
|
"album": {"queryset": music_models.Album.objects.all()},
|
|
"track": {"queryset": music_models.Track.objects.all()},
|
|
"library": {"queryset": music_models.Library.objects.all(), "id_attr": "uuid"},
|
|
"upload": {"queryset": music_models.Upload.objects.all(), "id_attr": "uuid"},
|
|
"account": {"queryset": models.Actor.objects.all(), "id_attr": "full_username"},
|
|
"channel": {"queryset": audio_models.Channel.objects.all(), "id_attr": "uuid"},
|
|
}
|
|
FETCH_OBJECT_FIELD = common_fields.GenericRelation(FETCH_OBJECT_CONFIG)
|
|
|
|
|
|
class FetchSerializer(serializers.ModelSerializer):
|
|
actor = federation_serializers.APIActorSerializer(read_only=True)
|
|
object = serializers.CharField(write_only=True)
|
|
force = serializers.BooleanField(default=False, required=False, write_only=True)
|
|
|
|
class Meta:
|
|
model = models.Fetch
|
|
fields = [
|
|
"id",
|
|
"url",
|
|
"actor",
|
|
"status",
|
|
"detail",
|
|
"creation_date",
|
|
"fetch_date",
|
|
"object",
|
|
"force",
|
|
]
|
|
read_only_fields = [
|
|
"id",
|
|
"url",
|
|
"actor",
|
|
"status",
|
|
"detail",
|
|
"creation_date",
|
|
"fetch_date",
|
|
]
|
|
|
|
def validate_object(self, value):
|
|
# if value is a webginfer lookup, we craft a special url
|
|
if value.startswith("@"):
|
|
value = value.lstrip("@")
|
|
validator = validators.EmailValidator()
|
|
try:
|
|
validator(value)
|
|
except validators.ValidationError:
|
|
return value
|
|
|
|
return f"webfinger://{value}"
|
|
|
|
def create(self, validated_data):
|
|
check_duplicates = not validated_data.get("force", False)
|
|
if check_duplicates:
|
|
# first we check for duplicates
|
|
duplicate = (
|
|
validated_data["actor"]
|
|
.fetches.filter(
|
|
status="finished",
|
|
url=validated_data["object"],
|
|
creation_date__gte=timezone.now()
|
|
- datetime.timedelta(
|
|
seconds=settings.FEDERATION_DUPLICATE_FETCH_DELAY
|
|
),
|
|
)
|
|
.order_by("-creation_date")
|
|
.first()
|
|
)
|
|
if duplicate:
|
|
return duplicate
|
|
|
|
fetch = models.Fetch.objects.create(
|
|
actor=validated_data["actor"], url=validated_data["object"]
|
|
)
|
|
return fetch
|
|
|
|
def to_representation(self, obj):
|
|
repr = super().to_representation(obj)
|
|
object_data = None
|
|
if obj.object:
|
|
object_data = FETCH_OBJECT_FIELD.to_representation(obj.object)
|
|
repr["object"] = object_data
|
|
return repr
|
|
|
|
|
|
class FullActorSerializer(serializers.Serializer):
|
|
fid = serializers.URLField()
|
|
url = serializers.URLField()
|
|
domain = serializers.CharField(source="domain_id")
|
|
creation_date = serializers.DateTimeField()
|
|
last_fetch_date = serializers.DateTimeField()
|
|
name = serializers.CharField()
|
|
preferred_username = serializers.CharField()
|
|
full_username = serializers.CharField()
|
|
type = serializers.CharField()
|
|
is_local = serializers.BooleanField()
|
|
is_channel = serializers.SerializerMethodField()
|
|
manually_approves_followers = serializers.BooleanField()
|
|
user = users_serializers.UserBasicSerializer()
|
|
summary = common_serializers.ContentSerializer(source="summary_obj")
|
|
icon = common_serializers.AttachmentSerializer(source="attachment_icon")
|
|
|
|
@extend_schema_field(OpenApiTypes.BOOL)
|
|
def get_is_channel(self, o):
|
|
try:
|
|
return bool(o.channel)
|
|
except ObjectDoesNotExist:
|
|
return False
|