Skip to content
GitLab
Menu
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
jovuit
funkwhale
Commits
8c93169d
Verified
Commit
8c93169d
authored
Apr 08, 2020
by
Eliot Berriot
Browse files
See #170: dispatch / handle delete and update on Audio
parent
b04ba47a
Changes
7
Hide whitespace changes
Inline
Side-by-side
api/funkwhale_api/federation/routes.py
View file @
8c93169d
import
logging
import
uuid
from
django.db.models
import
Q
...
...
@@ -333,6 +334,37 @@ def inbox_update_track(payload, context):
)
@
inbox
.
register
({
"type"
:
"Update"
,
"object.type"
:
"Audio"
})
def
inbox_update_audio
(
payload
,
context
):
serializer
=
serializers
.
ChannelCreateUploadSerializer
(
data
=
payload
,
context
=
context
)
if
not
serializer
.
is_valid
(
raise_exception
=
context
.
get
(
"raise_exception"
,
False
)):
logger
.
info
(
"Skipped update, invalid payload"
)
return
serializer
.
save
()
@
outbox
.
register
({
"type"
:
"Update"
,
"object.type"
:
"Audio"
})
def
outbox_update_audio
(
context
):
upload
=
context
[
"upload"
]
channel
=
upload
.
library
.
get_channel
()
actor
=
channel
.
actor
serializer
=
serializers
.
ChannelCreateUploadSerializer
(
upload
,
context
=
{
"type"
:
"Update"
,
"activity_id_suffix"
:
str
(
uuid
.
uuid4
())[:
8
]}
)
yield
{
"type"
:
"Update"
,
"actor"
:
actor
,
"payload"
:
with_recipients
(
serializer
.
data
,
to
=
[
activity
.
PUBLIC_ADDRESS
,
{
"type"
:
"instances_with_followers"
}],
),
}
@
inbox
.
register
({
"type"
:
"Update"
,
"object.type"
:
"Artist"
})
def
inbox_update_artist
(
payload
,
context
):
return
handle_library_entry_update
(
...
...
@@ -437,7 +469,6 @@ def outbox_delete_actor(context):
{
"type"
:
"Delete"
,
"object.type"
:
[
"Tombstone"
,
"Actor"
,
"Person"
,
"Application"
,
...
...
@@ -464,6 +495,17 @@ def inbox_delete_actor(payload, context):
actor
.
delete
()
@
inbox
.
register
({
"type"
:
"Delete"
,
"object.type"
:
"Tombstone"
})
def
inbox_delete
(
payload
,
context
):
serializer
=
serializers
.
DeleteSerializer
(
data
=
payload
,
context
=
context
)
if
not
serializer
.
is_valid
(
raise_exception
=
context
.
get
(
"raise_exception"
,
False
)):
logger
.
info
(
"Skipped deletion, invalid payload"
)
return
to_delete
=
serializer
.
validated_data
[
"object"
]
to_delete
.
delete
()
@
inbox
.
register
({
"type"
:
"Flag"
})
def
inbox_flag
(
payload
,
context
):
serializer
=
serializers
.
FlagSerializer
(
data
=
payload
,
context
=
context
)
...
...
api/funkwhale_api/federation/serializers.py
View file @
8c93169d
import
logging
import
os
import
urllib.parse
import
uuid
...
...
@@ -1967,7 +1968,6 @@ class ChannelUploadSerializer(jsonld.JsonLdSerializer):
class
ChannelCreateUploadSerializer
(
jsonld
.
JsonLdSerializer
):
type
=
serializers
.
ChoiceField
(
choices
=
[
contexts
.
AS
.
Create
])
object
=
serializers
.
DictField
()
class
Meta
:
...
...
@@ -1976,9 +1976,9 @@ class ChannelCreateUploadSerializer(jsonld.JsonLdSerializer):
}
def
to_representation
(
self
,
upload
):
return
{
payload
=
{
"@context"
:
jsonld
.
get_default_context
(),
"type"
:
"Create"
,
"type"
:
self
.
context
.
get
(
"type"
,
"Create"
)
,
"id"
:
utils
.
full_url
(
reverse
(
"federation:music:uploads-activity"
,
kwargs
=
{
"uuid"
:
upload
.
uuid
}
...
...
@@ -1989,6 +1989,12 @@ class ChannelCreateUploadSerializer(jsonld.JsonLdSerializer):
upload
,
context
=
{
"include_ap_context"
:
False
}
).
data
,
}
if
self
.
context
.
get
(
"activity_id_suffix"
):
payload
[
"id"
]
=
os
.
path
.
join
(
payload
[
"id"
],
self
.
context
[
"activity_id_suffix"
]
)
return
payload
def
validate
(
self
,
validated_data
):
serializer
=
ChannelUploadSerializer
(
...
...
@@ -1999,3 +2005,28 @@ class ChannelCreateUploadSerializer(jsonld.JsonLdSerializer):
def
save
(
self
,
**
kwargs
):
return
self
.
validated_data
[
"audio_serializer"
].
save
(
**
kwargs
)
class
DeleteSerializer
(
jsonld
.
JsonLdSerializer
):
object
=
serializers
.
URLField
(
max_length
=
500
)
type
=
serializers
.
ChoiceField
(
choices
=
[
contexts
.
AS
.
Delete
])
class
Meta
:
jsonld_mapping
=
{
"object"
:
jsonld
.
first_id
(
contexts
.
AS
.
object
)}
def
validate_object
(
self
,
url
):
try
:
obj
=
utils
.
get_object_by_fid
(
url
)
except
utils
.
ObjectDoesNotExist
:
raise
serializers
.
ValidationError
(
"No object matching {}"
.
format
(
url
))
if
isinstance
(
obj
,
music_models
.
Upload
):
obj
=
obj
.
track
return
obj
def
validate
(
self
,
validated_data
):
if
not
utils
.
can_manage
(
validated_data
[
"object"
].
attributed_to
,
self
.
context
[
"actor"
]
):
raise
serializers
.
ValidationError
(
"You cannot delete this object"
)
return
validated_data
api/funkwhale_api/federation/utils.py
View file @
8c93169d
...
...
@@ -277,3 +277,18 @@ def get_object_by_fid(fid, local=None):
return
channel
return
instance
def
can_manage
(
obj_owner
,
actor
):
if
not
obj_owner
:
return
False
if
not
actor
:
return
False
if
obj_owner
==
actor
:
return
True
if
obj_owner
.
domain
.
service_actor
==
actor
:
return
True
return
False
api/funkwhale_api/music/mutations.py
View file @
8c93169d
...
...
@@ -127,9 +127,18 @@ class TrackMutationSerializer(CoverMutation, TagMutation, DescriptionMutation):
return
serialized_relations
def
post_apply
(
self
,
obj
,
validated_data
):
routes
.
outbox
.
dispatch
(
{
"type"
:
"Update"
,
"object"
:
{
"type"
:
"Track"
}},
context
=
{
"track"
:
obj
}
)
channel
=
obj
.
artist
.
get_channel
()
if
channel
:
upload
=
channel
.
library
.
uploads
.
filter
(
track
=
obj
).
first
()
if
upload
:
routes
.
outbox
.
dispatch
(
{
"type"
:
"Update"
,
"object"
:
{
"type"
:
"Audio"
}},
context
=
{
"upload"
:
upload
},
)
else
:
routes
.
outbox
.
dispatch
(
{
"type"
:
"Update"
,
"object"
:
{
"type"
:
"Track"
}},
context
=
{
"track"
:
obj
}
)
@
mutations
.
registry
.
connect
(
...
...
api/tests/federation/test_routes.py
View file @
8c93169d
...
...
@@ -31,7 +31,9 @@ from funkwhale_api.moderation import serializers as moderation_serializers
({
"type"
:
"Update"
,
"object"
:
{
"type"
:
"Artist"
}},
routes
.
inbox_update_artist
),
({
"type"
:
"Update"
,
"object"
:
{
"type"
:
"Album"
}},
routes
.
inbox_update_album
),
({
"type"
:
"Update"
,
"object"
:
{
"type"
:
"Track"
}},
routes
.
inbox_update_track
),
({
"type"
:
"Update"
,
"object"
:
{
"type"
:
"Audio"
}},
routes
.
inbox_update_audio
),
({
"type"
:
"Delete"
,
"object"
:
{
"type"
:
"Person"
}},
routes
.
inbox_delete_actor
),
({
"type"
:
"Delete"
,
"object"
:
{
"type"
:
"Tombstone"
}},
routes
.
inbox_delete
),
({
"type"
:
"Flag"
},
routes
.
inbox_flag
),
],
)
...
...
@@ -62,6 +64,7 @@ def test_inbox_routes(route, handler):
({
"type"
:
"Delete"
,
"object"
:
{
"type"
:
"Album"
}},
routes
.
outbox_delete_album
),
({
"type"
:
"Undo"
,
"object"
:
{
"type"
:
"Follow"
}},
routes
.
outbox_undo_follow
),
({
"type"
:
"Update"
,
"object"
:
{
"type"
:
"Track"
}},
routes
.
outbox_update_track
),
({
"type"
:
"Update"
,
"object"
:
{
"type"
:
"Audio"
}},
routes
.
outbox_update_audio
),
(
{
"type"
:
"Delete"
,
"object"
:
{
"type"
:
"Tombstone"
}},
routes
.
outbox_delete_actor
,
...
...
@@ -354,7 +357,7 @@ def test_inbox_create_audio_channel(factories, mocker):
"@context"
:
jsonld
.
get_default_context
(),
"type"
:
"Create"
,
"actor"
:
channel
.
actor
.
fid
,
"object"
:
serializers
.
Channel
Create
UploadSerializer
(
upload
).
data
,
"object"
:
serializers
.
ChannelUploadSerializer
(
upload
).
data
,
}
upload
.
delete
()
init
=
mocker
.
spy
(
serializers
.
ChannelCreateUploadSerializer
,
"__init__"
)
...
...
@@ -368,7 +371,7 @@ def test_inbox_create_audio_channel(factories, mocker):
assert
init
.
call_count
==
1
args
=
init
.
call_args
assert
args
[
1
][
"data"
]
==
payload
[
"object"
]
assert
args
[
1
][
"data"
]
==
payload
assert
args
[
1
][
"context"
]
==
{
"channel"
:
channel
}
assert
save
.
call_count
==
1
...
...
@@ -765,6 +768,46 @@ def test_inbox_update_track(factories, mocker):
update_library_entity
.
assert_called_once_with
(
obj
,
{
"title"
:
"New title"
})
def
test_inbox_update_audio
(
factories
,
mocker
,
r_mock
):
channel
=
factories
[
"audio.Channel"
]()
upload
=
factories
[
"music.Upload"
](
library
=
channel
.
library
,
track__artist
=
channel
.
artist
,
track__attributed_to
=
channel
.
actor
,
)
upload
.
track
.
fid
=
upload
.
fid
upload
.
track
.
save
()
r_mock
.
get
(
upload
.
track
.
album
.
fid
,
json
=
serializers
.
AlbumSerializer
(
upload
.
track
.
album
).
data
,
)
data
=
serializers
.
ChannelCreateUploadSerializer
(
upload
).
data
data
[
"object"
][
"name"
]
=
"New title"
routes
.
inbox_update_audio
(
data
,
context
=
{
"actor"
:
channel
.
actor
,
"raise_exception"
:
True
}
)
upload
.
track
.
refresh_from_db
()
assert
upload
.
track
.
title
==
"New title"
def
test_outbox_update_audio
(
factories
,
faker
,
mocker
):
fake_uuid
=
faker
.
uuid4
()
mocker
.
patch
(
"uuid.uuid4"
,
return_value
=
fake_uuid
)
upload
=
factories
[
"music.Upload"
](
channel
=
True
)
activity
=
list
(
routes
.
outbox_update_audio
({
"upload"
:
upload
}))[
0
]
expected
=
serializers
.
ChannelCreateUploadSerializer
(
upload
).
data
expected
[
"type"
]
=
"Update"
expected
[
"id"
]
+=
"/"
+
fake_uuid
[:
8
]
expected
[
"to"
]
=
[
contexts
.
AS
.
Public
,
{
"type"
:
"instances_with_followers"
}]
assert
dict
(
activity
[
"payload"
])
==
dict
(
expected
)
assert
activity
[
"actor"
]
==
upload
.
library
.
channel
.
actor
def
test_outbox_update_track
(
factories
):
track
=
factories
[
"music.Track"
]()
activity
=
list
(
routes
.
outbox_update_track
({
"track"
:
track
}))[
0
]
...
...
api/tests/federation/test_third_party_activitypub.py
View file @
8c93169d
import
pytest
from
funkwhale_api.federation
import
routes
from
funkwhale_api.federation
import
serializers
...
...
@@ -141,3 +144,93 @@ def test_reel2bits_channel_from_actor_ap(db, mocker):
assert
channel
.
rss_url
==
payload
[
"url"
][
1
][
"href"
]
assert
channel
.
artist
.
name
==
actor
.
name
assert
channel
.
artist
.
attributed_to
==
actor
def
test_reel2bits_upload_create
(
factories
):
channel
=
factories
[
"audio.Channel"
]()
payload
=
{
"id"
:
"https://r2b.example/outbox/cb89c969224d7c9d"
,
"to"
:
[
"https://www.w3.org/ns/activitystreams#Public"
],
"type"
:
"Create"
,
"actor"
:
"https://r2b.example/user/anna"
,
"object"
:
{
"cc"
:
[
"https://r2b.example/user/anna/followers"
],
"id"
:
"https://r2b.example/outbox/cb89c969224d7c9d/activity"
,
"to"
:
[
"https://www.w3.org/ns/activitystreams#Public"
],
"url"
:
{
"href"
:
"https://r2b.example/uploads/sounds/anna/test.mp3"
,
"type"
:
"Link"
,
"mediaType"
:
"audio/mpeg"
,
},
"name"
:
"nya"
,
"tag"
:
[
{
"name"
:
"#nya"
,
"type"
:
"Hashtag"
},
{
"name"
:
"#cat"
,
"type"
:
"Hashtag"
},
{
"name"
:
"#paws"
,
"type"
:
"Hashtag"
},
],
"type"
:
"Audio"
,
"genre"
:
"cat"
,
"image"
:
{
"url"
:
"https://r2b.example/uploads/artwork_sounds/anna/test.jpg"
,
"type"
:
"Image"
,
"mediaType"
:
"image/jpeg"
,
},
"content"
:
"nya nya"
,
"licence"
:
{
"id"
:
"0"
,
"icon"
:
""
,
"link"
:
""
,
"name"
:
"Not Specified"
},
"mediaType"
:
"text/plain"
,
"published"
:
"2020-04-08T12:47:29Z"
,
"attributedTo"
:
"https://r2b.example/user/anna"
,
},
"@context"
:
[
"https://www.w3.org/ns/activitystreams"
,
"https://w3id.org/security/v1"
,
{
"toot"
:
"http://joinmastodon.org/ns#"
,
"Hashtag"
:
"as:Hashtag"
,
"featured"
:
"toot:featured"
,
"sensitive"
:
"as:sensitive"
,
},
],
"published"
:
"2020-04-08T12:47:29Z"
,
}
serializer
=
serializers
.
ChannelCreateUploadSerializer
(
data
=
payload
,
context
=
{
"channel"
:
channel
}
)
assert
serializer
.
is_valid
(
raise_exception
=
True
)
is
True
serializer
.
save
()
def
test_reel2bits_upload_delete
(
factories
):
actor
=
factories
[
"federation.Actor"
]()
channel
=
factories
[
"audio.Channel"
](
actor
=
actor
,
attributed_to
=
actor
)
upload
=
factories
[
"music.Upload"
](
channel
=
channel
,
track__attributed_to
=
actor
)
payload
=
{
"id"
:
"https://r2b.example/outbox/4987acc5b25f0aac"
,
"to"
:
[
"https://channels.tests.funkwhale.audio/federation/actors/demo"
,
"https://www.w3.org/ns/activitystreams#Public"
,
],
"type"
:
"Delete"
,
"actor"
:
actor
.
fid
,
"object"
:
{
"id"
:
upload
.
fid
,
"type"
:
"Tombstone"
},
"@context"
:
[
"https://www.w3.org/ns/activitystreams"
,
"https://w3id.org/security/v1"
,
{
"toot"
:
"http://joinmastodon.org/ns#"
,
"Hashtag"
:
"as:Hashtag"
,
"featured"
:
"toot:featured"
,
"sensitive"
:
"as:sensitive"
,
},
],
}
routes
.
inbox_delete
(
payload
,
context
=
{
"actor"
:
actor
,
"raise_exception"
:
True
,
"activity"
:
payload
},
)
with
pytest
.
raises
(
upload
.
track
.
DoesNotExist
):
upload
.
track
.
refresh_from_db
()
with
pytest
.
raises
(
upload
.
DoesNotExist
):
upload
.
refresh_from_db
()
api/tests/music/test_mutations.py
View file @
8c93169d
...
...
@@ -123,6 +123,19 @@ def test_track_mutation_apply_outbox(factories, mocker):
)
def
test_channel_track_mutation_apply_outbox
(
factories
,
mocker
):
dispatch
=
mocker
.
patch
(
"funkwhale_api.federation.routes.outbox.dispatch"
)
upload
=
factories
[
"music.Upload"
](
channel
=
True
,
track__position
=
4
)
mutation
=
factories
[
"common.Mutation"
](
type
=
"update"
,
target
=
upload
.
track
,
payload
=
{
"position"
:
12
}
)
mutation
.
apply
()
dispatch
.
assert_called_once_with
(
{
"type"
:
"Update"
,
"object"
:
{
"type"
:
"Audio"
}},
context
=
{
"upload"
:
upload
}
)
@
pytest
.
mark
.
parametrize
(
"factory_name"
,
[
"music.Artist"
,
"music.Album"
,
"music.Track"
])
def
test_mutation_set_tags
(
factory_name
,
factories
,
now
,
mocker
):
tags
=
[
"tag1"
,
"tag2"
]
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment