See #1100: clean compat and XXX in the code

This commit is contained in:
Agate 2020-08-03 15:47:14 +02:00
parent b7f1c02c6f
commit e271851f67
67 changed files with 203 additions and 328 deletions

View File

@ -74,11 +74,6 @@ v1_patterns += [
include(("funkwhale_api.history.urls", "history"), namespace="history"), include(("funkwhale_api.history.urls", "history"), namespace="history"),
), ),
url(r"^", include(("funkwhale_api.users.api_urls", "users"), namespace="users"),), url(r"^", include(("funkwhale_api.users.api_urls", "users"), namespace="users"),),
# XXX: 1.0: remove this
url(
r"^users/",
include(("funkwhale_api.users.api_urls", "users"), namespace="users-nested"),
),
url( url(
r"^oauth/", r"^oauth/",
include(("funkwhale_api.users.oauth.urls", "oauth"), namespace="oauth"), include(("funkwhale_api.users.oauth.urls", "oauth"), namespace="oauth"),

View File

@ -2,18 +2,13 @@ from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter from channels.routing import ProtocolTypeRouter, URLRouter
from django.conf.urls import url from django.conf.urls import url
from funkwhale_api.common.auth import TokenAuthMiddleware
from funkwhale_api.instance import consumers from funkwhale_api.instance import consumers
application = ProtocolTypeRouter( application = ProtocolTypeRouter(
{ {
# Empty for now (http->django views is added by default) # Empty for now (http->django views is added by default)
"websocket": AuthMiddlewareStack( "websocket": AuthMiddlewareStack(
TokenAuthMiddleware( URLRouter([url("^api/v1/activity$", consumers.InstanceActivityConsumer)])
URLRouter(
[url("^api/v1/activity$", consumers.InstanceActivityConsumer)]
)
)
) )
} }
) )

View File

@ -169,17 +169,7 @@ os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = env.bool(
"DJANGO_ALLOW_ASYNC_UNSAFE", default="true" "DJANGO_ALLOW_ASYNC_UNSAFE", default="true"
) )
# XXX: deprecated, see #186
FEDERATION_ENABLED = env.bool("FEDERATION_ENABLED", default=True)
FEDERATION_HOSTNAME = env("FEDERATION_HOSTNAME", default=FUNKWHALE_HOSTNAME).lower() FEDERATION_HOSTNAME = env("FEDERATION_HOSTNAME", default=FUNKWHALE_HOSTNAME).lower()
# XXX: deprecated, see #186
FEDERATION_COLLECTION_PAGE_SIZE = env.int("FEDERATION_COLLECTION_PAGE_SIZE", default=50)
# XXX: deprecated, see #186
FEDERATION_MUSIC_NEEDS_APPROVAL = env.bool(
"FEDERATION_MUSIC_NEEDS_APPROVAL", default=True
)
# XXX: deprecated, see #186
FEDERATION_ACTOR_FETCH_DELAY = env.int("FEDERATION_ACTOR_FETCH_DELAY", default=60 * 12)
FEDERATION_SERVICE_ACTOR_USERNAME = env( FEDERATION_SERVICE_ACTOR_USERNAME = env(
"FEDERATION_SERVICE_ACTOR_USERNAME", default="service" "FEDERATION_SERVICE_ACTOR_USERNAME", default="service"
) )
@ -1129,11 +1119,6 @@ Exemples:
CSRF_USE_SESSIONS = True CSRF_USE_SESSIONS = True
SESSION_ENGINE = "django.contrib.sessions.backends.cache" SESSION_ENGINE = "django.contrib.sessions.backends.cache"
# Playlist settings
# XXX: deprecated, see #186
PLAYLISTS_MAX_TRACKS = env.int("PLAYLISTS_MAX_TRACKS", default=250)
ACCOUNT_USERNAME_BLACKLIST = [ ACCOUNT_USERNAME_BLACKLIST = [
"funkwhale", "funkwhale",
"library", "library",
@ -1170,8 +1155,6 @@ EXTERNAL_REQUESTS_TIMEOUT = env.int("EXTERNAL_REQUESTS_TIMEOUT", default=10)
""" """
Default timeout for external requests. Default timeout for external requests.
""" """
# XXX: deprecated, see #186
API_AUTHENTICATION_REQUIRED = env.bool("API_AUTHENTICATION_REQUIRED", True)
MUSIC_DIRECTORY_PATH = env("MUSIC_DIRECTORY_PATH", default=None) MUSIC_DIRECTORY_PATH = env("MUSIC_DIRECTORY_PATH", default=None)
""" """
@ -1285,8 +1268,6 @@ FUNKWHALE_SUPPORT_MESSAGE_DELAY = env.int("FUNKWHALE_SUPPORT_MESSAGE_DELAY", def
""" """
Delay in days after signup before we show the "support Funkwhale" message Delay in days after signup before we show the "support Funkwhale" message
""" """
# XXX Stable release: remove
USE_FULL_TEXT_SEARCH = env.bool("USE_FULL_TEXT_SEARCH", default=True)
MIN_DELAY_BETWEEN_DOWNLOADS_COUNT = env.int( MIN_DELAY_BETWEEN_DOWNLOADS_COUNT = env.int(
"MIN_DELAY_BETWEEN_DOWNLOADS_COUNT", default=60 * 60 * 6 "MIN_DELAY_BETWEEN_DOWNLOADS_COUNT", default=60 * 60 * 6

View File

@ -1,19 +1,15 @@
from dynamic_preferences import types from dynamic_preferences import types
from dynamic_preferences.registries import global_preferences_registry from dynamic_preferences.registries import global_preferences_registry
from funkwhale_api.common import preferences
common = types.Section("common") common = types.Section("common")
@global_preferences_registry.register @global_preferences_registry.register
class APIAutenticationRequired( class APIAutenticationRequired(types.BooleanPreference):
preferences.DefaultFromSettingMixin, types.BooleanPreference
):
section = common section = common
name = "api_authentication_required" name = "api_authentication_required"
verbose_name = "API Requires authentication" verbose_name = "API Requires authentication"
setting = "API_AUTHENTICATION_REQUIRED" default = True
help_text = ( help_text = (
"If disabled, anonymous users will be able to query the API" "If disabled, anonymous users will be able to query the API"
"and access music data (as well as other data exposed in the API " "and access music data (as well as other data exposed in the API "

View File

@ -1,6 +1,5 @@
import django_filters import django_filters
from django import forms from django import forms
from django.conf import settings
from django.core.serializers.json import DjangoJSONEncoder from django.core.serializers.json import DjangoJSONEncoder
from django.db import models from django.db import models
@ -40,7 +39,7 @@ class SearchFilter(django_filters.CharFilter):
def filter(self, qs, value): def filter(self, qs, value):
if not value: if not value:
return qs return qs
if settings.USE_FULL_TEXT_SEARCH and self.fts_search_fields: if self.fts_search_fields:
query = search.get_fts_query( query = search.get_fts_query(
value, self.fts_search_fields, model=self.parent.Meta.model value, self.fts_search_fields, model=self.parent.Meta.model
) )

View File

@ -113,7 +113,7 @@ def get_spa_html(spa_url):
def get_spa_file(spa_url, name): def get_spa_file(spa_url, name):
if spa_url.startswith("/"): if spa_url.startswith("/"):
# XXX: spa_url is an absolute path to index.html, on the local disk. # spa_url is an absolute path to index.html, on the local disk.
# However, we may want to access manifest.json or other files as well, so we # However, we may want to access manifest.json or other files as well, so we
# strip the filename # strip the filename
path = os.path.join(os.path.dirname(spa_url), name) path = os.path.join(os.path.dirname(spa_url), name)

View File

@ -299,20 +299,6 @@ class AttachmentSerializer(serializers.Serializer):
urls["medium_square_crop"] = o.download_url_medium_square_crop urls["medium_square_crop"] = o.download_url_medium_square_crop
return urls return urls
def to_representation(self, o):
repr = super().to_representation(o)
# XXX: BACKWARD COMPATIBILITY
# having the attachment urls in a nested JSON obj is better,
# but we can't do this without breaking clients
# So we extract the urls and include these in the parent payload
repr.update({k: v for k, v in repr["urls"].items() if k != "source"})
# also, our legacy images had lots of variations (400x400, 200x200, 50x50)
# but we removed some of these, so we emulate these by hand (by redirecting)
# to actual, existing attachment variations
repr["square_crop"] = repr["medium_square_crop"]
repr["small_square_crop"] = repr["medium_square_crop"]
return repr
def create(self, validated_data): def create(self, validated_data):
return models.Attachment.objects.create( return models.Attachment.objects.create(
file=validated_data["file"], actor=validated_data["actor"] file=validated_data["file"], actor=validated_data["actor"]

View File

@ -13,8 +13,7 @@ class TrackFavoriteFilter(moderation_filters.HiddenContentFilterSet):
class Meta: class Meta:
model = models.TrackFavorite model = models.TrackFavorite
# XXX: 1.0 remove the user filter, we have scope=me now fields = []
fields = ["user"]
hidden_content_fields_mapping = moderation_filters.USER_FILTER_CONFIG[ hidden_content_fields_mapping = moderation_filters.USER_FILTER_CONFIG[
"TRACK_FAVORITE" "TRACK_FAVORITE"
] ]

View File

@ -1,8 +1,6 @@
from dynamic_preferences import types from dynamic_preferences import types
from dynamic_preferences.registries import global_preferences_registry from dynamic_preferences.registries import global_preferences_registry
from funkwhale_api.common import preferences
federation = types.Section("federation") federation = types.Section("federation")
@ -22,10 +20,10 @@ class MusicCacheDuration(types.IntPreference):
@global_preferences_registry.register @global_preferences_registry.register
class Enabled(preferences.DefaultFromSettingMixin, types.BooleanPreference): class Enabled(types.BooleanPreference):
section = federation section = federation
name = "enabled" name = "enabled"
setting = "FEDERATION_ENABLED" default = True
verbose_name = "Federation enabled" verbose_name = "Federation enabled"
help_text = ( help_text = (
"Use this setting to enable or disable federation logic and API" " globally." "Use this setting to enable or disable federation logic and API" " globally."
@ -33,20 +31,20 @@ class Enabled(preferences.DefaultFromSettingMixin, types.BooleanPreference):
@global_preferences_registry.register @global_preferences_registry.register
class CollectionPageSize(preferences.DefaultFromSettingMixin, types.IntPreference): class CollectionPageSize(types.IntPreference):
section = federation section = federation
name = "collection_page_size" name = "collection_page_size"
setting = "FEDERATION_COLLECTION_PAGE_SIZE" default = 50
verbose_name = "Federation collection page size" verbose_name = "Federation collection page size"
help_text = "How many items to display in ActivityPub collections." help_text = "How many items to display in ActivityPub collections."
field_kwargs = {"required": False} field_kwargs = {"required": False}
@global_preferences_registry.register @global_preferences_registry.register
class ActorFetchDelay(preferences.DefaultFromSettingMixin, types.IntPreference): class ActorFetchDelay(types.IntPreference):
section = federation section = federation
name = "actor_fetch_delay" name = "actor_fetch_delay"
setting = "FEDERATION_ACTOR_FETCH_DELAY" default = 60 * 12
verbose_name = "Federation actor fetch delay" verbose_name = "Federation actor fetch delay"
help_text = ( help_text = (
"How many minutes to wait before refetching actors on " "How many minutes to wait before refetching actors on "

View File

@ -938,8 +938,6 @@ class PaginatedCollectionSerializer(jsonld.JsonLdSerializer):
last = common_utils.set_query_parameter(conf["id"], page=paginator.num_pages) last = common_utils.set_query_parameter(conf["id"], page=paginator.num_pages)
d = { d = {
"id": conf["id"], "id": conf["id"],
# XXX Stable release: remove the obsolete actor field
"actor": conf["actor"].fid,
"attributedTo": conf["actor"].fid, "attributedTo": conf["actor"].fid,
"totalItems": paginator.count, "totalItems": paginator.count,
"type": conf.get("type", "Collection"), "type": conf.get("type", "Collection"),
@ -1004,9 +1002,8 @@ class LibrarySerializer(PaginatedCollectionSerializer):
"name": library.name, "name": library.name,
"summary": library.description, "summary": library.description,
"page_size": 100, "page_size": 100,
# XXX Stable release: remove the obsolete actor field
"actor": library.actor,
"attributedTo": library.actor, "attributedTo": library.actor,
"actor": library.actor,
"items": library.uploads.for_federation(), "items": library.uploads.for_federation(),
"type": "Library", "type": "Library",
} }
@ -1108,8 +1105,6 @@ class CollectionPageSerializer(jsonld.JsonLdSerializer):
], ],
} }
if conf["actor"]: if conf["actor"]:
# XXX Stable release: remove the obsolete actor field
d["actor"] = conf["actor"].fid
d["attributedTo"] = conf["actor"].fid d["attributedTo"] = conf["actor"].fid
if page.has_previous(): if page.has_previous():
@ -1297,8 +1292,7 @@ class AlbumSerializer(MusicEntitySerializer):
child=MultipleSerializer(allowed=[BasicActorSerializer, ArtistSerializer]), child=MultipleSerializer(allowed=[BasicActorSerializer, ArtistSerializer]),
min_length=1, min_length=1,
) )
# XXX: 1.0 rename to image image = ImageSerializer(
cover = ImageSerializer(
allowed_mimetypes=["image/*"], allowed_mimetypes=["image/*"],
allow_null=True, allow_null=True,
required=False, required=False,
@ -1306,7 +1300,7 @@ class AlbumSerializer(MusicEntitySerializer):
) )
updateable_fields = [ updateable_fields = [
("name", "title"), ("name", "title"),
("cover", "attachment_cover"), ("image", "attachment_cover"),
("musicbrainzId", "mbid"), ("musicbrainzId", "mbid"),
("attributedTo", "attributed_to"), ("attributedTo", "attributed_to"),
("released", "release_date"), ("released", "release_date"),
@ -1320,7 +1314,7 @@ class AlbumSerializer(MusicEntitySerializer):
{ {
"released": jsonld.first_val(contexts.FW.released), "released": jsonld.first_val(contexts.FW.released),
"artists": jsonld.first_attr(contexts.FW.artists, "@list"), "artists": jsonld.first_attr(contexts.FW.artists, "@list"),
"cover": jsonld.first_obj(contexts.FW.cover), "image": jsonld.first_obj(contexts.AS.image),
}, },
) )
@ -1354,11 +1348,6 @@ class AlbumSerializer(MusicEntitySerializer):
] ]
include_content(d, instance.description) include_content(d, instance.description)
if instance.attachment_cover: if instance.attachment_cover:
d["cover"] = {
"type": "Link",
"href": instance.attachment_cover.download_url_original,
"mediaType": instance.attachment_cover.mimetype or "image/jpeg",
}
include_image(d, instance.attachment_cover) include_image(d, instance.attachment_cover)
if self.context.get("include_ap_context", self.parent is None): if self.context.get("include_ap_context", self.parent is None):

View File

@ -218,7 +218,6 @@ def should_redirect_ap_to_html(accept_header, default=True):
"text/html", "text/html",
] ]
no_redirect_headers = [ no_redirect_headers = [
"*/*", # XXX backward compat with older Funkwhale instances that don't send the Accept header
"application/json", "application/json",
"application/activity+json", "application/activity+json",
"application/ld+json", "application/ld+json",

View File

@ -0,0 +1,22 @@
# Generated by Django 3.0.8 on 2020-08-03 12:22
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('moderation', '0005_auto_20200317_0820'),
]
operations = [
migrations.RemoveField(
model_name='userrequest',
name='url',
),
migrations.AlterField(
model_name='userrequest',
name='status',
field=models.CharField(choices=[('pending', 'Pending'), ('refused', 'Refused'), ('approved', 'Approved')], default='pending', max_length=40),
),
]

View File

@ -20,7 +20,6 @@ from django.db.models.signals import post_save, pre_save
from django.dispatch import receiver from django.dispatch import receiver
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from versatileimagefield.fields import VersatileImageField
from funkwhale_api import musicbrainz from funkwhale_api import musicbrainz
from funkwhale_api.common import fields from funkwhale_api.common import fields
@ -325,10 +324,6 @@ class Album(APIModelMixin):
artist = models.ForeignKey(Artist, related_name="albums", on_delete=models.CASCADE) artist = models.ForeignKey(Artist, related_name="albums", on_delete=models.CASCADE)
release_date = models.DateField(null=True, blank=True, db_index=True) release_date = models.DateField(null=True, blank=True, db_index=True)
release_group_id = models.UUIDField(null=True, blank=True) release_group_id = models.UUIDField(null=True, blank=True)
# XXX: 1.0 clean this uneeded field in favor of attachment_cover
cover = VersatileImageField(
upload_to="albums/covers/%Y/%m/%d", null=True, blank=True
)
attachment_cover = models.ForeignKey( attachment_cover = models.ForeignKey(
"common.Attachment", "common.Attachment",
null=True, null=True,

View File

@ -32,10 +32,7 @@ COVER_WRITE_FIELD = common_serializers.RelatedField(
from funkwhale_api.audio import serializers as audio_serializers # NOQA from funkwhale_api.audio import serializers as audio_serializers # NOQA
class CoverField( class CoverField(common_serializers.AttachmentSerializer):
common_serializers.NullToEmptDict, common_serializers.AttachmentSerializer
):
# XXX: BACKWARD COMPATIBILITY
pass pass

View File

@ -364,7 +364,7 @@ def federation_audio_track_to_metadata(payload, references):
"mbid": str(payload["album"]["musicbrainzId"]) "mbid": str(payload["album"]["musicbrainzId"])
if payload["album"].get("musicbrainzId") if payload["album"].get("musicbrainzId")
else None, else None,
"cover_data": get_cover(payload["album"], "cover"), "cover_data": get_cover(payload["album"], "image"),
"release_date": payload["album"].get("released"), "release_date": payload["album"].get("released"),
"tags": [t["name"] for t in payload["album"].get("tags", []) or []], "tags": [t["name"] for t in payload["album"].get("tags", []) or []],
"artists": [ "artists": [
@ -896,8 +896,6 @@ UPDATE_CONFIG = {
@transaction.atomic @transaction.atomic
def update_track_metadata(audio_metadata, track): def update_track_metadata(audio_metadata, track):
# XXX: implement this to support updating metadata when an imported files
# is updated by an outside tool (e.g beets).
serializer = metadata.TrackMetadataSerializer(data=audio_metadata) serializer = metadata.TrackMetadataSerializer(data=audio_metadata)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
new_data = serializer.validated_data new_data = serializer.validated_data

View File

@ -797,20 +797,11 @@ class Search(views.APIView):
return Response(results, status=200) return Response(results, status=200)
def get_tracks(self, query): def get_tracks(self, query):
search_fields = [ query_obj = utils.get_fts_query(
"mbid", query,
"title__unaccent", fts_fields=["body_text", "album__body_text", "artist__body_text"],
"album__title__unaccent", model=models.Track,
"artist__name__unaccent", )
]
if settings.USE_FULL_TEXT_SEARCH:
query_obj = utils.get_fts_query(
query,
fts_fields=["body_text", "album__body_text", "artist__body_text"],
model=models.Track,
)
else:
query_obj = utils.get_query(query, search_fields)
qs = ( qs = (
models.Track.objects.all() models.Track.objects.all()
.filter(query_obj) .filter(query_obj)
@ -828,13 +819,9 @@ class Search(views.APIView):
return common_utils.order_for_search(qs, "title")[: self.max_results] return common_utils.order_for_search(qs, "title")[: self.max_results]
def get_albums(self, query): def get_albums(self, query):
search_fields = ["mbid", "title__unaccent", "artist__name__unaccent"] query_obj = utils.get_fts_query(
if settings.USE_FULL_TEXT_SEARCH: query, fts_fields=["body_text", "artist__body_text"], model=models.Album
query_obj = utils.get_fts_query( )
query, fts_fields=["body_text", "artist__body_text"], model=models.Album
)
else:
query_obj = utils.get_query(query, search_fields)
qs = ( qs = (
models.Album.objects.all() models.Album.objects.all()
.filter(query_obj) .filter(query_obj)
@ -844,11 +831,7 @@ class Search(views.APIView):
return common_utils.order_for_search(qs, "title")[: self.max_results] return common_utils.order_for_search(qs, "title")[: self.max_results]
def get_artists(self, query): def get_artists(self, query):
search_fields = ["mbid", "name__unaccent"] query_obj = utils.get_fts_query(query, model=models.Artist)
if settings.USE_FULL_TEXT_SEARCH:
query_obj = utils.get_fts_query(query, model=models.Artist)
else:
query_obj = utils.get_query(query, search_fields)
qs = ( qs = (
models.Artist.objects.all() models.Artist.objects.all()
.filter(query_obj) .filter(query_obj)

View File

@ -1,16 +1,14 @@
from dynamic_preferences import types from dynamic_preferences import types
from dynamic_preferences.registries import global_preferences_registry from dynamic_preferences.registries import global_preferences_registry
from funkwhale_api.common import preferences
playlists = types.Section("playlists") playlists = types.Section("playlists")
@global_preferences_registry.register @global_preferences_registry.register
class MaxTracks(preferences.DefaultFromSettingMixin, types.IntegerPreference): class MaxTracks(types.IntegerPreference):
show_in_api = True show_in_api = True
section = playlists section = playlists
name = "max_tracks" name = "max_tracks"
default = 250
verbose_name = "Max tracks per playlist" verbose_name = "Max tracks per playlist"
setting = "PLAYLISTS_MAX_TRACKS"
field_kwargs = {"required": False} field_kwargs = {"required": False}

View File

@ -31,7 +31,6 @@ class PlaylistFilter(filters.FilterSet):
class Meta: class Meta:
model = models.Playlist model = models.Playlist
fields = { fields = {
"user": ["exact"],
"name": ["exact", "icontains"], "name": ["exact", "icontains"],
} }

View File

@ -227,7 +227,6 @@ class PlaylistTrack(models.Model):
class Meta: class Meta:
ordering = ("-playlist", "index") ordering = ("-playlist", "index")
unique_together = ("playlist", "index")
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):
playlist = self.playlist playlist = self.playlist

View File

@ -0,0 +1,20 @@
# Generated by Django 3.0.8 on 2020-08-03 12:22
import django.contrib.postgres.fields.jsonb
import django.core.serializers.json
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('radios', '0004_auto_20180107_1813'),
]
operations = [
migrations.AlterField(
model_name='radio',
name='config',
field=django.contrib.postgres.fields.jsonb.JSONField(encoder=django.core.serializers.json.DjangoJSONEncoder),
),
]

View File

@ -0,0 +1,25 @@
# Generated by Django 3.0.8 on 2020-08-03 12:22
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
('tags', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='taggeditem',
name='content_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tagged_items', to='contenttypes.ContentType', verbose_name='Content type'),
),
migrations.AlterField(
model_name='taggeditem',
name='tag',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tagged_items', to='tags.Tag'),
),
]

View File

@ -1,30 +0,0 @@
import pytest
from rest_framework_jwt.settings import api_settings
from funkwhale_api.common.auth import TokenAuthMiddleware
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
@pytest.mark.parametrize("query_string", [b"token=wrong", b""])
def test_header_anonymous(query_string, factories):
def callback(scope):
assert scope["user"].is_anonymous
scope = {"query_string": query_string}
consumer = TokenAuthMiddleware(callback)
consumer(scope)
def test_header_correct_token(factories):
user = factories["users.User"]()
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
def callback(scope):
assert scope["user"] == user
scope = {"query_string": "token={}".format(token).encode("utf-8")}
consumer = TokenAuthMiddleware(callback)
consumer(scope)

View File

@ -17,7 +17,7 @@ from django import urls
"/api/v1/auth/registration/account-confirm-email/key", "/api/v1/auth/registration/account-confirm-email/key",
"/api/v1/history/listenings", "/api/v1/history/listenings",
"/api/v1/radios/sessions", "/api/v1/radios/sessions",
"/api/v1/users/users/me", "/api/v1/users/me",
"/api/v1/federation/follows/library", "/api/v1/federation/follows/library",
"/api/v1/manage/accounts", "/api/v1/manage/accounts",
"/api/v1/oauth/apps", "/api/v1/oauth/apps",

View File

@ -201,15 +201,6 @@ def test_attachment_serializer_existing_file(factories, to_api_date):
attachment.file.crop["200x200"].url attachment.file.crop["200x200"].url
), ),
}, },
# XXX: BACKWARD COMPATIBILITY
"original": federation_utils.full_url(attachment.file.url),
"medium_square_crop": federation_utils.full_url(
attachment.file.crop["200x200"].url
),
"small_square_crop": federation_utils.full_url(
attachment.file.crop["200x200"].url
),
"square_crop": federation_utils.full_url(attachment.file.crop["200x200"].url),
} }
serializer = serializers.AttachmentSerializer(attachment) serializer = serializers.AttachmentSerializer(attachment)
@ -237,17 +228,6 @@ def test_attachment_serializer_remote_file(factories, to_api_date):
proxy_url + "?next=medium_square_crop" proxy_url + "?next=medium_square_crop"
), ),
}, },
# XXX: BACKWARD COMPATIBILITY
"original": federation_utils.full_url(proxy_url + "?next=original"),
"medium_square_crop": federation_utils.full_url(
proxy_url + "?next=medium_square_crop"
),
"square_crop": federation_utils.full_url(
proxy_url + "?next=medium_square_crop"
),
"small_square_crop": federation_utils.full_url(
proxy_url + "?next=medium_square_crop"
),
} }
serializer = serializers.AttachmentSerializer(attachment) serializer = serializers.AttachmentSerializer(attachment)

View File

@ -20,10 +20,11 @@ def test_user_can_get_his_favorites(
api_request, factories, logged_in_api_client, client api_request, factories, logged_in_api_client, client
): ):
request = api_request.get("/") request = api_request.get("/")
logged_in_api_client.user.create_actor()
favorite = factories["favorites.TrackFavorite"](user=logged_in_api_client.user) favorite = factories["favorites.TrackFavorite"](user=logged_in_api_client.user)
factories["favorites.TrackFavorite"]() factories["favorites.TrackFavorite"]()
url = reverse("api:v1:favorites:tracks-list") url = reverse("api:v1:favorites:tracks-list")
response = logged_in_api_client.get(url, {"user": logged_in_api_client.user.pk}) response = logged_in_api_client.get(url, {"scope": "me"})
expected = [ expected = [
serializers.UserTrackFavoriteSerializer( serializers.UserTrackFavoriteSerializer(
favorite, context={"request": request} favorite, context={"request": request}

View File

@ -388,7 +388,6 @@ def test_paginated_collection_serializer(factories):
"@context": jsonld.get_default_context(), "@context": jsonld.get_default_context(),
"type": "Collection", "type": "Collection",
"id": conf["id"], "id": conf["id"],
"actor": actor.fid,
"attributedTo": actor.fid, "attributedTo": actor.fid,
"totalItems": len(uploads), "totalItems": len(uploads),
"current": conf["id"] + "?page=1", "current": conf["id"] + "?page=1",
@ -486,7 +485,6 @@ def test_collection_page_serializer(factories):
"@context": jsonld.get_default_context(), "@context": jsonld.get_default_context(),
"type": "CollectionPage", "type": "CollectionPage",
"id": conf["id"] + "?page=2", "id": conf["id"] + "?page=2",
"actor": actor.fid,
"attributedTo": actor.fid, "attributedTo": actor.fid,
"totalItems": len(uploads), "totalItems": len(uploads),
"partOf": conf["id"], "partOf": conf["id"],
@ -521,7 +519,6 @@ def test_music_library_serializer_to_ap(factories):
"id": library.fid, "id": library.fid,
"name": library.name, "name": library.name,
"summary": library.description, "summary": library.description,
"actor": library.actor.fid,
"attributedTo": library.actor.fid, "attributedTo": library.actor.fid,
"totalItems": 0, "totalItems": 0,
"current": library.fid + "?page=1", "current": library.fid + "?page=1",
@ -764,11 +761,6 @@ def test_activity_pub_album_serializer_to_ap(factories):
"type": "Album", "type": "Album",
"id": album.fid, "id": album.fid,
"name": album.title, "name": album.title,
"cover": {
"type": "Link",
"mediaType": "image/jpeg",
"href": utils.full_url(album.attachment_cover.file.url),
},
"image": { "image": {
"type": "Image", "type": "Image",
"mediaType": "image/jpeg", "mediaType": "image/jpeg",
@ -815,7 +807,7 @@ def test_activity_pub_album_serializer_from_ap_create(factories, faker, now):
"type": "Album", "type": "Album",
"id": "https://album.example", "id": "https://album.example",
"name": faker.sentence(), "name": faker.sentence(),
"cover": {"type": "Link", "mediaType": "image/jpeg", "href": faker.url()}, "image": {"type": "Link", "mediaType": "image/jpeg", "href": faker.url()},
"musicbrainzId": faker.uuid4(), "musicbrainzId": faker.uuid4(),
"published": now.isoformat(), "published": now.isoformat(),
"released": released.isoformat(), "released": released.isoformat(),
@ -839,8 +831,8 @@ def test_activity_pub_album_serializer_from_ap_create(factories, faker, now):
assert str(album.mbid) == payload["musicbrainzId"] assert str(album.mbid) == payload["musicbrainzId"]
assert album.release_date == released assert album.release_date == released
assert album.artist == artist assert album.artist == artist
assert album.attachment_cover.url == payload["cover"]["href"] assert album.attachment_cover.url == payload["image"]["href"]
assert album.attachment_cover.mimetype == payload["cover"]["mediaType"] assert album.attachment_cover.mimetype == payload["image"]["mediaType"]
assert sorted(album.tagged_items.values_list("tag__name", flat=True)) == [ assert sorted(album.tagged_items.values_list("tag__name", flat=True)) == [
"Punk", "Punk",
"Rock", "Rock",
@ -879,7 +871,7 @@ def test_activity_pub_album_serializer_from_ap_update(factories, faker):
"type": "Album", "type": "Album",
"id": album.fid, "id": album.fid,
"name": faker.sentence(), "name": faker.sentence(),
"cover": {"type": "Link", "mediaType": "image/jpeg", "href": faker.url()}, "image": {"type": "Link", "mediaType": "image/jpeg", "href": faker.url()},
"musicbrainzId": faker.uuid4(), "musicbrainzId": faker.uuid4(),
"published": album.creation_date.isoformat(), "published": album.creation_date.isoformat(),
"released": released.isoformat(), "released": released.isoformat(),
@ -904,8 +896,8 @@ def test_activity_pub_album_serializer_from_ap_update(factories, faker):
assert album.title == payload["name"] assert album.title == payload["name"]
assert str(album.mbid) == payload["musicbrainzId"] assert str(album.mbid) == payload["musicbrainzId"]
assert album.release_date == released assert album.release_date == released
assert album.attachment_cover.url == payload["cover"]["href"] assert album.attachment_cover.url == payload["image"]["href"]
assert album.attachment_cover.mimetype == payload["cover"]["mediaType"] assert album.attachment_cover.mimetype == payload["image"]["mediaType"]
assert sorted(album.tagged_items.values_list("tag__name", flat=True)) == [ assert sorted(album.tagged_items.values_list("tag__name", flat=True)) == [
"Punk", "Punk",
"Rock", "Rock",
@ -996,7 +988,7 @@ def test_activity_pub_track_serializer_from_ap(factories, r_mock, mocker):
"content": "Album summary", "content": "Album summary",
"mediaType": "text/markdown", "mediaType": "text/markdown",
"attributedTo": album_attributed_to.fid, "attributedTo": album_attributed_to.fid,
"cover": { "image": {
"type": "Link", "type": "Link",
"href": "https://cover.image/test.png", "href": "https://cover.image/test.png",
"mediaType": "image/png", "mediaType": "image/png",
@ -1066,8 +1058,8 @@ def test_activity_pub_track_serializer_from_ap(factories, r_mock, mocker):
assert track.attachment_cover.mimetype == data["image"]["mediaType"] assert track.attachment_cover.mimetype == data["image"]["mediaType"]
assert album.from_activity == activity assert album.from_activity == activity
assert album.attachment_cover.url == data["album"]["cover"]["href"] assert album.attachment_cover.url == data["album"]["image"]["href"]
assert album.attachment_cover.mimetype == data["album"]["cover"]["mediaType"] assert album.attachment_cover.mimetype == data["album"]["image"]["mediaType"]
assert album.title == data["album"]["name"] assert album.title == data["album"]["name"]
assert album.fid == data["album"]["id"] assert album.fid == data["album"]["id"]
assert str(album.mbid) == data["album"]["musicbrainzId"] assert str(album.mbid) == data["album"]["musicbrainzId"]
@ -1196,7 +1188,7 @@ def test_activity_pub_upload_serializer_from_ap(factories, mocker, r_mock):
"musicbrainzId": str(uuid.uuid4()), "musicbrainzId": str(uuid.uuid4()),
"published": published.isoformat(), "published": published.isoformat(),
"released": released.isoformat(), "released": released.isoformat(),
"cover": { "image": {
"type": "Link", "type": "Link",
"href": "https://cover.image/test.png", "href": "https://cover.image/test.png",
"mediaType": "image/png", "mediaType": "image/png",
@ -1222,7 +1214,7 @@ def test_activity_pub_upload_serializer_from_ap(factories, mocker, r_mock):
], ],
}, },
} }
r_mock.get(data["track"]["album"]["cover"]["href"], body=io.BytesIO(b"coucou")) r_mock.get(data["track"]["album"]["image"]["href"], body=io.BytesIO(b"coucou"))
serializer = serializers.UploadSerializer(data=data, context={"activity": activity}) serializer = serializers.UploadSerializer(data=data, context={"activity": activity})
assert serializer.is_valid(raise_exception=True) assert serializer.is_valid(raise_exception=True)
@ -1266,7 +1258,7 @@ def test_activity_pub_upload_serializer_from_ap_update(factories, mocker, now, r
"library": library.fid, "library": library.fid,
"track": serializers.TrackSerializer(upload.track).data, "track": serializers.TrackSerializer(upload.track).data,
} }
r_mock.get(data["track"]["album"]["cover"]["href"], body=io.BytesIO(b"coucou")) r_mock.get(data["track"]["album"]["image"]["url"], body=io.BytesIO(b"coucou"))
serializer = serializers.UploadSerializer(upload, data=data) serializer = serializers.UploadSerializer(upload, data=data)
assert serializer.is_valid(raise_exception=True) assert serializer.is_valid(raise_exception=True)
@ -1628,7 +1620,6 @@ def test_channel_actor_outbox_serializer(factories):
"@context": jsonld.get_default_context(), "@context": jsonld.get_default_context(),
"type": "OrderedCollection", "type": "OrderedCollection",
"id": channel.actor.outbox_url, "id": channel.actor.outbox_url,
"actor": channel.actor.fid,
"attributedTo": channel.actor.fid, "attributedTo": channel.actor.fid,
"totalItems": len(uploads), "totalItems": len(uploads),
"first": channel.actor.outbox_url + "?page=1", "first": channel.actor.outbox_url + "?page=1",

View File

@ -384,11 +384,6 @@ def test_music_upload_detail_private_approved_follow(
("text/html,application/xhtml+xml", True, True), ("text/html,application/xhtml+xml", True, True),
("text/html,application/json", True, True), ("text/html,application/json", True, True),
("", True, False), ("", True, False),
(
"*/*",
True,
False,
), # XXX: compat with older versions of Funkwhale that miss the Accept header
(None, True, False), (None, True, False),
("application/json", True, False), ("application/json", True, False),
("application/activity+json", True, False), ("application/activity+json", True, False),

View File

@ -196,15 +196,6 @@ def test_album_serializer(factories, to_api_date):
assert serializer.data == expected assert serializer.data == expected
def test_album_serializer_empty_cover(factories, to_api_date):
# XXX: BACKWARD COMPATIBILITY
album = factories["music.Album"](attachment_cover=None)
serializer = serializers.AlbumSerializer(album)
assert serializer.data["cover"] == {}
def test_track_serializer(factories, to_api_date): def test_track_serializer(factories, to_api_date):
actor = factories["federation.Actor"]() actor = factories["federation.Actor"]()
upload = factories["music.Upload"]( upload = factories["music.Upload"](

View File

@ -660,7 +660,7 @@ def test_federation_audio_track_to_metadata(now, mocker):
}, },
} }
], ],
"cover": { "image": {
"type": "Link", "type": "Link",
"href": "http://cover.test", "href": "http://cover.test",
"mediaType": "image/png", "mediaType": "image/png",
@ -713,8 +713,8 @@ def test_federation_audio_track_to_metadata(now, mocker):
"tags": ["AlbumTag"], "tags": ["AlbumTag"],
"description": {"content_type": "text/plain", "text": "album desc"}, "description": {"content_type": "text/plain", "text": "album desc"},
"cover_data": { "cover_data": {
"mimetype": serializer.validated_data["album"]["cover"]["mediaType"], "mimetype": serializer.validated_data["album"]["image"]["mediaType"],
"url": serializer.validated_data["album"]["cover"]["href"], "url": serializer.validated_data["album"]["image"]["href"],
}, },
"artists": [ "artists": [
{ {

View File

@ -1307,9 +1307,7 @@ def test_get_upload_audio_metadata(logged_in_api_client, factories):
assert response.data == serializer.validated_data assert response.data == serializer.validated_data
@pytest.mark.parametrize("use_fts", [True, False]) def test_search_get(logged_in_api_client, factories):
def test_search_get(use_fts, settings, logged_in_api_client, factories):
settings.USE_FULL_TEXT_SEARCH = use_fts
artist = factories["music.Artist"](name="Foo Fighters") artist = factories["music.Artist"](name="Foo Fighters")
album = factories["music.Album"](title="Foo Bar") album = factories["music.Album"](title="Foo Bar")
track = factories["music.Track"](title="Foo Baz") track = factories["music.Track"](title="Foo Baz")
@ -1332,8 +1330,7 @@ def test_search_get(use_fts, settings, logged_in_api_client, factories):
assert response.data == expected assert response.data == expected
def test_search_get_fts_advanced(settings, logged_in_api_client, factories): def test_search_get_fts_advanced(logged_in_api_client, factories):
settings.USE_FULL_TEXT_SEARCH = True
artist1 = factories["music.Artist"](name="Foo Bighters") artist1 = factories["music.Artist"](name="Foo Bighters")
artist2 = factories["music.Artist"](name="Bar Fighter") artist2 = factories["music.Artist"](name="Bar Fighter")
factories["music.Artist"]() factories["music.Artist"]()
@ -1353,8 +1350,7 @@ def test_search_get_fts_advanced(settings, logged_in_api_client, factories):
assert response.data == expected assert response.data == expected
def test_search_get_fts_stop_words(settings, logged_in_api_client, factories): def test_search_get_fts_stop_words(logged_in_api_client, factories):
settings.USE_FULL_TEXT_SEARCH = True
artist = factories["music.Artist"](name="she") artist = factories["music.Artist"](name="she")
factories["music.Artist"](name="something else") factories["music.Artist"](name="something else")

View File

@ -720,7 +720,7 @@ User:
type: "string" type: "string"
example: "Alice Kingsley" example: "Alice Kingsley"
avatar: avatar:
$ref: "#/Avatar" $ref: "#/Attachment"
Me: Me:
type: "object" type: "object"
@ -767,30 +767,8 @@ Me:
via request headers isn't possible. via request headers isn't possible.
The token expires after 3 days by default. The token expires after 3 days by default.
Avatar:
type: "object" QuotaStatus:
properties:
original:
type: "string"
format: "uri"
description: "Original image URL"
example: "http://yourinstance/media/users/avatars/92/49/60/b3c-4736-43b3-bb5c-ed7a99ac6996.jpg"
square_crop:
type: "string"
format: "uri"
description: "400x400 thumbnail URL"
example: "http://yourinstance/media/__sized__/users/avatars/92/49/60/b3c-4736-43b3-bb5c-ed7a99ac6996-crop-c0-5__0-5-400x400-70.jpg"
small_square_crop:
type: "string"
format: "uri"
description: "50x50 thumbnail URL"
example: "http://yourinstance/media/__sized__/users/avatars/92/49/60/b3c-4736-43b3-bb5c-ed7a99ac6996-crop-c0-5__0-5-50x50-70.jpg"
medium_square_crop:
type: "string"
format: "uri"
description: "200x200 thumbnail URL"
example: "http://yourinstance/media/__sized__/users/avatars/92/49/60/b3c-4736-43b3-bb5c-ed7a99ac6996-crop-c0-5__0-5-200x200-70.jpg"
QuotaStatus:
type: "object" type: "object"
properties: properties:
max: max:

View File

@ -296,7 +296,7 @@ paths:
responses: responses:
200: 200:
$ref: "#/responses/200" $ref: "#/responses/200"
/api/v1/users/users/me/: /api/v1/users/me/:
get: get:
summary: Retrive profile information summary: Retrive profile information
description: | description: |

View File

@ -337,7 +337,7 @@ export default {
}, },
getCover(albumCover) { getCover(albumCover) {
if (albumCover) { if (albumCover) {
return albumCover.medium_square_crop return albumCover.urls.medium_square_crop
} }
}, },
getSources (uploads) { getSources (uploads) {

View File

@ -6,8 +6,8 @@
<div class="ui six wide column current-track"> <div class="ui six wide column current-track">
<div class="ui basic segment" id="player"> <div class="ui basic segment" id="player">
<template v-if="currentTrack"> <template v-if="currentTrack">
<img ref="cover" alt="" v-if="currentTrack.cover && currentTrack.cover.original" :src="$store.getters['instance/absoluteUrl'](currentTrack.cover.medium_square_crop)"> <img ref="cover" alt="" v-if="currentTrack.cover && currentTrack.cover.urls.original" :src="$store.getters['instance/absoluteUrl'](currentTrack.cover.urls.medium_square_crop)">
<img ref="cover" alt="" v-else-if="currentTrack.album && currentTrack.album.cover && currentTrack.album.cover.original" :src="$store.getters['instance/absoluteUrl'](currentTrack.album.cover.medium_square_crop)"> <img ref="cover" alt="" v-else-if="currentTrack.album && currentTrack.album.cover && currentTrack.album.cover.urls.original" :src="$store.getters['instance/absoluteUrl'](currentTrack.album.cover.urls.medium_square_crop)">
<img class="ui image" alt="" v-else src="../assets/audio/default-cover.png"> <img class="ui image" alt="" v-else src="../assets/audio/default-cover.png">
<h1 class="ui header"> <h1 class="ui header">
<div class="content ellipsis"> <div class="content ellipsis">
@ -159,8 +159,8 @@
<i class="grip lines icon"></i> <i class="grip lines icon"></i>
</td> </td>
<td class="image-cell" @click="$store.dispatch('queue/currentIndex', index)"> <td class="image-cell" @click="$store.dispatch('queue/currentIndex', index)">
<img class="ui mini image" alt="" v-if="track.cover && track.cover.original" :src="$store.getters['instance/absoluteUrl'](track.cover.medium_square_crop)"> <img class="ui mini image" alt="" v-if="track.cover && track.cover.urls.original" :src="$store.getters['instance/absoluteUrl'](track.cover.urls.medium_square_crop)">
<img class="ui mini image" alt="" v-else-if="track.album && track.album.cover && track.album.cover.original" :src="$store.getters['instance/absoluteUrl'](track.album.cover.medium_square_crop)"> <img class="ui mini image" alt="" v-else-if="track.album && track.album.cover && track.album.cover.urls.original" :src="$store.getters['instance/absoluteUrl'](track.album.cover.urls.medium_square_crop)">
<img class="ui mini image" alt="" v-else src="../assets/audio/default-cover.png"> <img class="ui mini image" alt="" v-else src="../assets/audio/default-cover.png">
</td> </td>
<td colspan="3" @click="$store.dispatch('queue/currentIndex', index)"> <td colspan="3" @click="$store.dispatch('queue/currentIndex', index)">

View File

@ -78,7 +78,7 @@
</router-link> </router-link>
<div class="item"> <div class="item">
<div class="ui user-dropdown dropdown" > <div class="ui user-dropdown dropdown" >
<img class="ui avatar image" alt="" v-if="$store.state.auth.profile.avatar && $store.state.auth.profile.avatar.square_crop" :src="$store.getters['instance/absoluteUrl']($store.state.auth.profile.avatar.square_crop)" /> <img class="ui avatar image" alt="" v-if="$store.state.auth.profile.avatar && $store.state.auth.profile.avatar.urls.square_crop" :src="$store.getters['instance/absoluteUrl']($store.state.auth.profile.avatar.urls.square_crop)" />
<actor-avatar v-else :actor="{preferred_username: $store.state.auth.username, full_username: $store.state.auth.username}" /> <actor-avatar v-else :actor="{preferred_username: $store.state.auth.username, full_username: $store.state.auth.username}" />
<div class="menu"> <div class="menu">
<router-link class="item" :to="{name: 'profile.overview', params: {username: $store.state.auth.username}}"><translate translate-context="*/*/*/Noun">Profile</translate></router-link> <router-link class="item" :to="{name: 'profile.overview', params: {username: $store.state.auth.username}}"><translate translate-context="*/*/*/Noun">Profile</translate></router-link>

View File

@ -1,6 +1,6 @@
<template> <template>
<router-link class="artist-label ui image label" :to="route"> <router-link class="artist-label ui image label" :to="route">
<img :class="[{circular: artist.content_category != 'podcast'}]" alt="" v-if="artist.cover && artist.cover.original" v-lazy="$store.getters['instance/absoluteUrl'](artist.cover.small_square_crop)" /> <img alt="" :class="[{circular: artist.content_category != 'podcast'}]" v-if="artist.cover && artist.cover.urls.original" v-lazy="$store.getters['instance/absoluteUrl'](artist.cover.urls.medium_square_crop)" />
<i :class="[artist.content_category != 'podcast' ? 'circular' : 'bordered', 'inverted violet users icon']" v-else /> <i :class="[artist.content_category != 'podcast' ? 'circular' : 'bordered', 'inverted violet users icon']" v-else />
{{ artist.name }} {{ artist.name }}
</router-link> </router-link>

View File

@ -60,7 +60,7 @@ export default {
computed: { computed: {
imageUrl () { imageUrl () {
if (this.object.artist.cover) { if (this.object.artist.cover) {
return this.$store.getters['instance/absoluteUrl'](this.object.artist.cover.medium_square_crop) return this.$store.getters['instance/absoluteUrl'](this.object.artist.cover.urls.medium_square_crop)
} }
}, },
urlId () { urlId () {

View File

@ -7,8 +7,8 @@
@click="$router.push({name: 'library.tracks.detail', params: {id: entry.id}})" @click="$router.push({name: 'library.tracks.detail', params: {id: entry.id}})"
alt="" alt=""
class="channel-image image" class="channel-image image"
v-if="cover && cover.original" v-if="cover && cover.urls.original"
v-lazy="$store.getters['instance/absoluteUrl'](cover.square_crop)"> v-lazy="$store.getters['instance/absoluteUrl'](cover.urls.square_crop)">
<span <span
@click="$router.push({name: 'library.tracks.detail', params: {id: entry.id}})" @click="$router.push({name: 'library.tracks.detail', params: {id: entry.id}})"
class="channel-image image" class="channel-image image"
@ -17,8 +17,8 @@
@click="$router.push({name: 'library.tracks.detail', params: {id: entry.id}})" @click="$router.push({name: 'library.tracks.detail', params: {id: entry.id}})"
alt="" alt=""
class="channel-image image" class="channel-image image"
v-else-if="entry.album && entry.album.cover && entry.album.cover.original" v-else-if="entry.album && entry.album.cover && entry.album.cover.urls.original"
v-lazy="$store.getters['instance/absoluteUrl'](entry.album.cover.square_crop)"> v-lazy="$store.getters['instance/absoluteUrl'](entry.album.cover.urls.square_crop)">
<img <img
@click="$router.push({name: 'library.tracks.detail', params: {id: entry.id}})" @click="$router.push({name: 'library.tracks.detail', params: {id: entry.id}})"
alt="" alt=""

View File

@ -1,10 +1,10 @@
<template> <template>
<div class="channel-serie-card"> <div class="channel-serie-card">
<div class="two-images"> <div class="two-images">
<img @click="$router.push({name: 'library.albums.detail', params: {id: serie.id}})" class="channel-image" alt="" v-if="cover.original" v-lazy="$store.getters['instance/absoluteUrl'](cover.square_crop)"> <img alt="" @click="$router.push({name: 'library.albums.detail', params: {id: serie.id}})" class="channel-image" v-if="cover.urls.original" v-lazy="$store.getters['instance/absoluteUrl'](cover.urls.square_crop)">
<img @click="$router.push({name: 'library.albums.detail', params: {id: serie.id}})" class="channel-image" alt="" v-else src="../../assets/audio/default-cover.png"> <img alt="" @click="$router.push({name: 'library.albums.detail', params: {id: serie.id}})" class="channel-image" v-else src="../../assets/audio/default-cover.png">
<img @click="$router.push({name: 'library.albums.detail', params: {id: serie.id}})" class="channel-image" alt="" v-if="cover.original" v-lazy="$store.getters['instance/absoluteUrl'](cover.square_crop)"> <img alt="" @click="$router.push({name: 'library.albums.detail', params: {id: serie.id}})" class="channel-image" v-if="cover.urls.original" v-lazy="$store.getters['instance/absoluteUrl'](cover.urls.square_crop)">
<img @click="$router.push({name: 'library.albums.detail', params: {id: serie.id}})" class="channel-image" alt="" v-else src="../../assets/audio/default-cover.png"> <img alt="" @click="$router.push({name: 'library.albums.detail', params: {id: serie.id}})" class="channel-image" v-else src="../../assets/audio/default-cover.png">
</div> </div>
<div class="content ellipsis"> <div class="content ellipsis">
<strong> <strong>

View File

@ -10,9 +10,9 @@
<div class="controls track-controls queue-not-focused desktop-and-up"> <div class="controls track-controls queue-not-focused desktop-and-up">
<div class="ui tiny image" @click.stop.prevent="$router.push({name: 'library.tracks.detail', params: {id: currentTrack.id }})"> <div class="ui tiny image" @click.stop.prevent="$router.push({name: 'library.tracks.detail', params: {id: currentTrack.id }})">
<img ref="cover" alt="" v-if="currentTrack.cover && currentTrack.cover.original" :src="$store.getters['instance/absoluteUrl'](currentTrack.cover.medium_square_crop)"> <img alt="" ref="cover" v-if="currentTrack.cover && currentTrack.cover.urls.original" :src="$store.getters['instance/absoluteUrl'](currentTrack.cover.urls.medium_square_crop)">
<img ref="cover" alt="" v-else-if="currentTrack.album && currentTrack.album.cover && currentTrack.album.cover.original" :src="$store.getters['instance/absoluteUrl'](currentTrack.album.cover.medium_square_crop)"> <img alt="" ref="cover" v-else-if="currentTrack.album && currentTrack.album.cover.urls && currentTrack.album.cover.urls.original" :src="$store.getters['instance/absoluteUrl'](currentTrack.album.cover.urls.medium_square_crop)">
<img v-else src="../../assets/audio/default-cover.png" alt=""> <img alt="" v-else src="../../assets/audio/default-cover.png">
</div> </div>
<div @click.stop.prevent="" class="middle aligned content ellipsis"> <div @click.stop.prevent="" class="middle aligned content ellipsis">
<strong> <strong>
@ -30,9 +30,9 @@
</div> </div>
<div class="controls track-controls queue-not-focused tablet-and-below"> <div class="controls track-controls queue-not-focused tablet-and-below">
<div class="ui tiny image"> <div class="ui tiny image">
<img ref="cover" alt="" v-if="currentTrack.cover && currentTrack.cover.original" :src="$store.getters['instance/absoluteUrl'](currentTrack.cover.medium_square_crop)"> <img alt="" ref="cover" v-if="currentTrack.cover && currentTrack.cover.urls.original" :src="$store.getters['instance/absoluteUrl'](currentTrack.cover.urls.medium_square_crop)">
<img ref="cover" alt="" v-else-if="currentTrack.album && currentTrack.album.cover && currentTrack.album.cover.original" :src="$store.getters['instance/absoluteUrl'](currentTrack.album.cover.medium_square_crop)"> <img alt="" ref="cover" v-else-if="currentTrack.album && currentTrack.album.cover && currentTrack.album.cover.urls.original" :src="$store.getters['instance/absoluteUrl'](currentTrack.album.cover.urls.medium_square_crop)">
<img v-else src="../../assets/audio/default-cover.png" alt=""> <img alt="" v-else src="../../assets/audio/default-cover.png">
</div> </div>
<div class="middle aligned content ellipsis"> <div class="middle aligned content ellipsis">
<strong> <strong>
@ -738,12 +738,12 @@ export default {
if (this.currentTrack.album) { if (this.currentTrack.album) {
metadata.album = this.currentTrack.album.title metadata.album = this.currentTrack.album.title
metadata.artwork = [ metadata.artwork = [
{ src: this.currentTrack.album.cover.original, sizes: '96x96', type: 'image/png' }, { src: this.currentTrack.album.cover.urls.original, sizes: '96x96', type: 'image/png' },
{ src: this.currentTrack.album.cover.original, sizes: '128x128', type: 'image/png' }, { src: this.currentTrack.album.cover.urls.original, sizes: '128x128', type: 'image/png' },
{ src: this.currentTrack.album.cover.original, sizes: '192x192', type: 'image/png' }, { src: this.currentTrack.album.cover.urls.original, sizes: '192x192', type: 'image/png' },
{ src: this.currentTrack.album.cover.original, sizes: '256x256', type: 'image/png' }, { src: this.currentTrack.album.cover.urls.original, sizes: '256x256', type: 'image/png' },
{ src: this.currentTrack.album.cover.original, sizes: '384x384', type: 'image/png' }, { src: this.currentTrack.album.cover.urls.original, sizes: '384x384', type: 'image/png' },
{ src: this.currentTrack.album.cover.original, sizes: '512x512', type: 'image/png' }, { src: this.currentTrack.album.cover.urls.original, sizes: '512x512', type: 'image/png' },
] ]
} }
navigator.mediaSession.metadata = new MediaMetadata(metadata); navigator.mediaSession.metadata = new MediaMetadata(metadata);

View File

@ -2,7 +2,7 @@
<div class="card app-card component-album-card"> <div class="card app-card component-album-card">
<div <div
@click="$router.push({name: 'library.albums.detail', params: {id: album.id}})" @click="$router.push({name: 'library.albums.detail', params: {id: album.id}})"
:class="['ui', 'head-image', 'image', {'default-cover': !album.cover.original}]" v-lazy:background-image="imageUrl"> :class="['ui', 'head-image', 'image', {'default-cover': !album.cover || !album.cover.urls.original}]" v-lazy:background-image="imageUrl">
<play-button :icon-only="true" :is-playable="album.is_playable" :button-classes="['ui', 'circular', 'large', 'vibrant', 'icon', 'button']" :album="album"></play-button> <play-button :icon-only="true" :is-playable="album.is_playable" :button-classes="['ui', 'circular', 'large', 'vibrant', 'icon', 'button']" :album="album"></play-button>
</div> </div>
<div class="content"> <div class="content">
@ -38,8 +38,8 @@ export default {
}, },
computed: { computed: {
imageUrl () { imageUrl () {
if (this.album.cover.original) { if (this.album.cover && this.album.cover.urls.original) {
return this.$store.getters['instance/absoluteUrl'](this.album.cover.medium_square_crop) return this.$store.getters['instance/absoluteUrl'](this.album.cover.urls.medium_square_crop)
} }
} }
} }

View File

@ -2,7 +2,7 @@
<div class="app-card card"> <div class="app-card card">
<div <div
@click="$router.push({name: 'library.artists.detail', params: {id: artist.id}})" @click="$router.push({name: 'library.artists.detail', params: {id: artist.id}})"
:class="['ui', 'head-image', 'circular', 'image', {'default-cover': !cover.original}]" v-lazy:background-image="imageUrl"> :class="['ui', 'head-image', 'circular', 'image', {'default-cover': !cover || !cover.urls.original}]" v-lazy:background-image="imageUrl">
<play-button :icon-only="true" :is-playable="artist.is_playable" :button-classes="['ui', 'circular', 'large', 'vibrant', 'icon', 'button']" :artist="artist"></play-button> <play-button :icon-only="true" :is-playable="artist.is_playable" :button-classes="['ui', 'circular', 'large', 'vibrant', 'icon', 'button']" :artist="artist"></play-button>
</div> </div>
<div class="content"> <div class="content">
@ -40,19 +40,19 @@ export default {
computed: { computed: {
imageUrl () { imageUrl () {
let cover = this.cover let cover = this.cover
if (cover.original) { if (cover && cover.urls.original) {
return this.$store.getters['instance/absoluteUrl'](cover.medium_square_crop) return this.$store.getters['instance/absoluteUrl'](cover.urls.medium_square_crop)
} }
}, },
cover () { cover () {
if (this.artist.cover && this.artist.cover.original) { if (this.artist.cover && this.artist.cover.urls.original) {
return this.artist.cover return this.artist.cover
} }
return this.artist.albums.map((a) => { return this.artist.albums.map((a) => {
return a.cover return a.cover
}).filter((c) => { }).filter((c) => {
return c && c.original return c && c.urls.original
})[0] || {} })[0]
}, },
} }
} }

View File

@ -4,8 +4,8 @@
<play-button :class="['basic', {vibrant: currentTrack && isPlaying && track.id === currentTrack.id}, 'icon']" :discrete="true" :is-playable="playable" :track="track"></play-button> <play-button :class="['basic', {vibrant: currentTrack && isPlaying && track.id === currentTrack.id}, 'icon']" :discrete="true" :is-playable="playable" :track="track"></play-button>
</td> </td>
<td> <td>
<img class="ui mini image" alt="" v-if="track.album && track.album.cover.original" v-lazy="$store.getters['instance/absoluteUrl'](track.album.cover.small_square_crop)"> <img alt="" class="ui mini image" v-if="track.album && track.album.cover && track.album.cover.urls.original" v-lazy="$store.getters['instance/absoluteUrl'](track.album.cover.urls.medium_square_crop)">
<img class="ui mini image" alt="" v-else src="../../../assets/audio/default-cover.png"> <img alt="" class="ui mini image" v-else src="../../../assets/audio/default-cover.png">
</td> </td>
<td colspan="6"> <td colspan="6">
<router-link class="track" :to="{name: 'library.tracks.detail', params: {id: track.id }}"> <router-link class="track" :to="{name: 'library.tracks.detail', params: {id: track.id }}">

View File

@ -7,7 +7,7 @@
<div v-if="count > 0" class="ui divided unstackable items"> <div v-if="count > 0" class="ui divided unstackable items">
<div :class="['item', itemClasses]" v-for="object in objects" :key="object.id"> <div :class="['item', itemClasses]" v-for="object in objects" :key="object.id">
<div class="ui tiny image"> <div class="ui tiny image">
<img alt="" v-if="object.track.album && object.track.album.cover.original" v-lazy="$store.getters['instance/absoluteUrl'](object.track.album.cover.medium_square_crop)"> <img alt="" v-if="object.track.album && object.track.album.cover" v-lazy="$store.getters['instance/absoluteUrl'](object.track.album.cover.urls.medium_square_crop)">
<img alt="" v-else src="../../../assets/audio/default-cover.png"> <img alt="" v-else src="../../../assets/audio/default-cover.png">
<play-button class="play-overlay" :icon-only="true" :button-classes="['ui', 'circular', 'tiny', 'vibrant', 'icon', 'button']" :track="object.track"></play-button> <play-button class="play-overlay" :icon-only="true" :button-classes="['ui', 'circular', 'tiny', 'vibrant', 'icon', 'button']" :track="object.track"></play-button>
</div> </div>

View File

@ -416,12 +416,12 @@ export default {
this.settings.errors = [] this.settings.errors = []
let self = this let self = this
let payload = this.settingsValues let payload = this.settingsValues
let url = `users/users/${this.$store.state.auth.username}/` let url = `users/${this.$store.state.auth.username}/`
return axios.patch(url, payload).then( return axios.patch(url, payload).then(
response => { response => {
logger.default.info("Updated settings successfully") logger.default.info("Updated settings successfully")
self.settings.success = true self.settings.success = true
return axios.get("users/users/me/").then(response => { return axios.get("users/me/").then(response => {
self.$store.dispatch("auth/updateProfile", response.data) self.$store.dispatch("auth/updateProfile", response.data)
}) })
}, },
@ -487,7 +487,7 @@ export default {
this.avatarErrors = [] this.avatarErrors = []
let self = this let self = this
axios axios
.patch(`users/users/${this.$store.state.auth.username}/`, {avatar: uuid}) .patch(`users/${this.$store.state.auth.username}/`, {avatar: uuid})
.then( .then(
response => { response => {
this.isLoadingAvatar = false this.isLoadingAvatar = false
@ -538,7 +538,7 @@ export default {
confirm: true, confirm: true,
password: this.password, password: this.password,
} }
axios.delete(`users/users/me/`, {data: payload}) axios.delete(`users/me/`, {data: payload})
.then( .then(
response => { response => {
self.isDeletingAccount = false self.isDeletingAccount = false

View File

@ -87,7 +87,7 @@ export default {
this.errors = [] this.errors = []
this.isLoading = true this.isLoading = true
let self = this let self = this
let url = `users/users/${this.$store.state.auth.username}/subsonic-token/` let url = `users/${this.$store.state.auth.username}/subsonic-token/`
return axios.get(url).then(response => { return axios.get(url).then(response => {
self.token = response.data['subsonic_api_token'] self.token = response.data['subsonic_api_token']
self.isLoading = false self.isLoading = false
@ -102,7 +102,7 @@ export default {
this.errors = [] this.errors = []
this.isLoading = true this.isLoading = true
let self = this let self = this
let url = `users/users/${this.$store.state.auth.username}/subsonic-token/` let url = `users/${this.$store.state.auth.username}/subsonic-token/`
return axios.post(url, {}).then(response => { return axios.post(url, {}).then(response => {
self.showToken = true self.showToken = true
self.token = response.data['subsonic_api_token'] self.token = response.data['subsonic_api_token']
@ -119,7 +119,7 @@ export default {
this.errors = [] this.errors = []
this.isLoading = true this.isLoading = true
let self = this let self = this
let url = `users/users/${this.$store.state.auth.username}/subsonic-token/` let url = `users/${this.$store.state.auth.username}/subsonic-token/`
return axios.delete(url).then(response => { return axios.delete(url).then(response => {
self.isLoading = false self.isLoading = false
self.token = null self.token = null

View File

@ -347,7 +347,7 @@ export default {
}, },
fetchQuota () { fetchQuota () {
let self = this let self = this
axios.get('users/users/me/').then((response) => { axios.get('users/me/').then((response) => {
self.quotaStatus = response.data.quota_status self.quotaStatus = response.data.quota_status
}) })
}, },
@ -391,8 +391,8 @@ export default {
value: c.uuid, value: c.uuid,
selected: self.channel && self.channel.uuid === c.uuid, selected: self.channel && self.channel.uuid === c.uuid,
} }
if (c.artist.cover && c.artist.cover.small_square_crop) { if (c.artist.cover && c.artist.cover.urls.medium_square_crop) {
let coverUrl = self.$store.getters['instance/absoluteUrl'](c.artist.cover.small_square_crop) let coverUrl = self.$store.getters['instance/absoluteUrl'](c.artist.cover.urls.medium_square_crop)
d.image = coverUrl d.image = coverUrl
if (c.artist.content_category === 'podcast') { if (c.artist.content_category === 'podcast') {
d.imageClass = 'ui image' d.imageClass = 'ui image'

View File

@ -1,5 +1,5 @@
<template> <template>
<img alt="" v-if="actor.icon && actor.icon.original" :src="actor.icon.small_square_crop" class="ui avatar circular image" /> <img alt="" v-if="actor.icon && actor.icon.urls.original" :src="actor.icon.urls.medium_square_crop" class="ui avatar circular image" />
<span v-else :style="defaultAvatarStyle" class="ui avatar circular label">{{ actor.preferred_username[0]}}</span> <span v-else :style="defaultAvatarStyle" class="ui avatar circular label">{{ actor.preferred_username[0]}}</span>
</template> </template>

View File

@ -4,8 +4,8 @@
<img <img
class="ui tiny circular avatar" class="ui tiny circular avatar"
alt="" alt=""
v-if="user.avatar && user.avatar.small_square_crop" v-if="user.avatar && user.avatar.urls.medium_square_crop"
v-lazy="$store.getters['instance/absoluteUrl'](user.avatar.small_square_crop)" /> v-lazy="$store.getters['instance/absoluteUrl'](user.avatar.urls.medium_square_crop)" />
<span v-else :style="defaultAvatarStyle" class="ui circular label">{{ user.username[0]}}</span> <span v-else :style="defaultAvatarStyle" class="ui circular label">{{ user.username[0]}}</span>
&nbsp; &nbsp;
</template> </template>

View File

@ -11,10 +11,10 @@
<div class="ui two column grid" v-if="isSerie"> <div class="ui two column grid" v-if="isSerie">
<div class="column"> <div class="column">
<div class="large two-images"> <div class="large two-images">
<img class="channel-image" alt="" v-if="object.cover && object.cover.original" v-lazy="$store.getters['instance/absoluteUrl'](object.cover.square_crop)"> <img alt="" class="channel-image" v-if="object.cover && object.cover.urls.original" v-lazy="$store.getters['instance/absoluteUrl'](object.cover.urls.square_crop)">
<img class="channel-image" alt="" v-else src="../../assets/audio/default-cover.png"> <img alt="" class="channel-image" v-else src="../../assets/audio/default-cover.png">
<img class="channel-image" alt="" v-if="object.cover && object.cover.original" v-lazy="$store.getters['instance/absoluteUrl'](object.cover.square_crop)"> <img alt="" class="channel-image" v-if="object.cover && object.cover.urls.original" v-lazy="$store.getters['instance/absoluteUrl'](object.cover.urls.square_crop)">
<img class="channel-image" alt="" v-else src="../../assets/audio/default-cover.png"> <img alt="" class="channel-image" v-else src="../../assets/audio/default-cover.png">
</div> </div>
</div> </div>
<div class="ui column right aligned"> <div class="ui column right aligned">
@ -53,8 +53,8 @@
</header> </header>
</div> </div>
<div v-else class="ui center aligned text padded basic segment"> <div v-else class="ui center aligned text padded basic segment">
<img class="channel-image" alt="" v-if="object.cover && object.cover.original" v-lazy="$store.getters['instance/absoluteUrl'](object.cover.square_crop)"> <img alt="" class="channel-image" v-if="object.cover && object.cover.urls.original" v-lazy="$store.getters['instance/absoluteUrl'](object.cover.urls.square_crop)">
<img class="channel-image" alt="" v-else src="../../assets/audio/default-cover.png"> <img alt="" class="channel-image" v-else src="../../assets/audio/default-cover.png">
<div class="ui hidden divider"></div> <div class="ui hidden divider"></div>
<header> <header>
<h2 class="ui header" :title="object.title"> <h2 class="ui header" :title="object.title">

View File

@ -235,12 +235,12 @@ export default {
) )
}, },
cover() { cover() {
if (this.object.cover && this.object.cover.original) { if (this.object.cover && this.object.cover.urls.original) {
return this.object.cover return this.object.cover
} }
return this.object.albums return this.object.albums
.filter(album => { .filter(album => {
return album.cover && album.cover.original return album.cover && album.cover.urls.original
}) })
.map(album => { .map(album => {
return album.cover return album.cover
@ -253,12 +253,12 @@ export default {
}) })
}, },
headerStyle() { headerStyle() {
if (!this.cover || !this.cover.original) { if (!this.cover || !this.cover.urls.original) {
return "" return ""
} }
return ( return (
"background-image: url(" + "background-image: url(" +
this.$store.getters["instance/absoluteUrl"](this.cover.original) + this.$store.getters["instance/absoluteUrl"](this.cover.urls.original) +
")" ")"
) )
}, },

View File

@ -274,7 +274,7 @@ export default {
fetchQuota () { fetchQuota () {
let self = this let self = this
self.isLoadingQuota = true self.isLoadingQuota = true
axios.get('users/users/me/').then((response) => { axios.get('users/me/').then((response) => {
self.quotaStatus = response.data.quota_status self.quotaStatus = response.data.quota_status
self.isLoadingQuota = false self.isLoadingQuota = false
}) })

View File

@ -264,12 +264,12 @@ export default {
return route.href return route.href
}, },
headerStyle() { headerStyle() {
if (!this.cover || !this.cover.original) { if (!this.cover || !this.cover.urls.original) {
return "" return ""
} }
return ( return (
"background-image: url(" + "background-image: url(" +
this.$store.getters["instance/absoluteUrl"](this.cover.original) + this.$store.getters["instance/absoluteUrl"](this.cover.urls.original) +
")" ")"
) )
}, },

View File

@ -4,7 +4,7 @@
<section class="ui vertical stripe segment"> <section class="ui vertical stripe segment">
<div class="ui stackable grid row container"> <div class="ui stackable grid row container">
<div class="six wide column"> <div class="six wide column">
<img class="image" alt="" v-if="cover && cover.original" v-lazy="$store.getters['instance/absoluteUrl'](cover.square_crop)"> <img alt="" class="image" v-if="cover && cover.urls.original" v-lazy="$store.getters['instance/absoluteUrl'](cover.urls.square_crop)">
<template v-if="upload"> <template v-if="upload">
<h3 class="ui header"> <h3 class="ui header">
<translate key="1" v-if="track.artist.content_category === 'music'" translate-context="Content/*/*">Track Details</translate> <translate key="1" v-if="track.artist.content_category === 'music'" translate-context="Content/*/*">Track Details</translate>
@ -223,7 +223,7 @@ export default {
return this.licenseData return this.licenseData
}, },
cover () { cover () {
if (this.track.cover && this.track.cover.original) { if (this.track.cover && this.track.cover.urls.original) {
return this.track.cover return this.track.cover
} }
if (this.track.album && this.track.album.cover) { if (this.track.album && this.track.album.cover) {

View File

@ -64,7 +64,7 @@
<tr v-for="(plt, index) in plts" :key="`${index}-${plt.track.id}`"> <tr v-for="(plt, index) in plts" :key="`${index}-${plt.track.id}`">
<td class="left aligned">{{ plt.index + 1}}</td> <td class="left aligned">{{ plt.index + 1}}</td>
<td class="center aligned"> <td class="center aligned">
<img alt="" class="ui mini image" v-if="plt.track.album && plt.track.album.cover.original" v-lazy="$store.getters['instance/absoluteUrl'](plt.track.album.cover.small_square_crop)"> <img alt="" class="ui mini image" v-if="plt.track.album && plt.track.album.cover.urls.original" v-lazy="$store.getters['instance/absoluteUrl'](plt.track.album.cover.urls.medium_square_crop)">
<img alt="" class="ui mini image" v-else src="../../assets/audio/default-cover.png"> <img alt="" class="ui mini image" v-else src="../../assets/audio/default-cover.png">
</td> </td>
<td colspan="4"> <td colspan="4">

View File

@ -182,7 +182,7 @@ export default {
fetchProfile ({commit, dispatch, state}) { fetchProfile ({commit, dispatch, state}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
axios.get('users/users/me/').then((response) => { axios.get('users/me/').then((response) => {
logger.default.info('Successfully fetched user profile') logger.default.info('Successfully fetched user profile')
dispatch('ui/initSettings', response.data.settings, { root: true }) dispatch('ui/initSettings', response.data.settings, { root: true })
dispatch('updateProfile', response.data).then(() => { dispatch('updateProfile', response.data).then(() => {

View File

@ -33,7 +33,7 @@ export default {
let playlists = [] let playlists = []
let url = 'playlists/' let url = 'playlists/'
while (url != null) { while (url != null) {
let response = await axios.get(url, {params: {user: userId}}) let response = await axios.get(url, {params: {scope: "me"}})
playlists = [...playlists, ...response.data.results] playlists = [...playlists, ...response.data.results]
url = response.data.next url = response.data.next

View File

@ -167,7 +167,7 @@ export default {
} }
payload[field] = newDisplayDate payload[field] = newDisplayDate
let self = this let self = this
axios.patch(`users/users/${this.$store.state.auth.username}/`, payload).then((response) => { axios.patch(`users/${this.$store.state.auth.username}/`, payload).then((response) => {
self.$store.commit('auth/profilePartialUpdate', response.data) self.$store.commit('auth/profilePartialUpdate', response.data)
}) })
}, },

View File

@ -9,7 +9,7 @@
<div class="ui column"> <div class="ui column">
<div class="segment-content"> <div class="segment-content">
<h2 class="ui header"> <h2 class="ui header">
<img alt="" v-if="object.artist.cover && object.artist.cover.square_crop" v-lazy="$store.getters['instance/absoluteUrl'](object.artist.cover.square_crop)"> <img alt="" v-if="object.artist.cover && object.artist.cover.urls.square_crop" v-lazy="$store.getters['instance/absoluteUrl'](object.artist.cover.urls.square_crop)">
<img alt="" v-else src="../../assets/audio/default-cover.png"> <img alt="" v-else src="../../assets/audio/default-cover.png">
<div class="content"> <div class="content">
{{ object.artist.name | truncate(100) }} {{ object.artist.name | truncate(100) }}

View File

@ -9,7 +9,7 @@
<div class="ui column"> <div class="ui column">
<div class="segment-content"> <div class="segment-content">
<h2 class="ui header"> <h2 class="ui header">
<img alt="" v-if="object.cover.original" v-lazy="$store.getters['instance/absoluteUrl'](object.cover.square_crop)"> <img alt="" v-if="object.cover.urls.original" v-lazy="$store.getters['instance/absoluteUrl'](object.cover.urls.square_crop)">
<img alt="" v-else src="../../../assets/audio/default-cover.png"> <img alt="" v-else src="../../../assets/audio/default-cover.png">
<div class="content"> <div class="content">
{{ object.title | truncate(100) }} {{ object.title | truncate(100) }}

View File

@ -9,7 +9,7 @@
<div class="ui column"> <div class="ui column">
<div class="segment-content"> <div class="segment-content">
<h2 class="ui header"> <h2 class="ui header">
<img alt="" v-if="object.cover && object.cover.square_crop" v-lazy="$store.getters['instance/absoluteUrl'](object.cover.square_crop)"> <img alt="" v-if="object.cover && object.cover.urls.square_crop" v-lazy="$store.getters['instance/absoluteUrl'](object.cover.urls.square_crop)">
<img alt="" v-else src="../../../assets/audio/default-cover.png"> <img alt="" v-else src="../../../assets/audio/default-cover.png">
<div class="content"> <div class="content">
{{ object.name | truncate(100) }} {{ object.name | truncate(100) }}

View File

@ -9,7 +9,7 @@
<div class="ui column"> <div class="ui column">
<div class="segment-content"> <div class="segment-content">
<h2 class="ui header"> <h2 class="ui header">
<img alt="" v-if="object.cover && object.cover.square_crop" v-lazy="$store.getters['instance/absoluteUrl'](object.cover.square_crop)"> <img alt="" v-if="object.cover && object.cover.urls.square_crop" v-lazy="$store.getters['instance/absoluteUrl'](object.cover.urls.square_crop)">
<img alt="" v-else src="../../../assets/audio/default-cover.png"> <img alt="" v-else src="../../../assets/audio/default-cover.png">
<div class="content"> <div class="content">
{{ object.title | truncate(100) }} {{ object.title | truncate(100) }}

View File

@ -27,7 +27,7 @@
</div> </div>
<h1 class="ui center aligned icon header"> <h1 class="ui center aligned icon header">
<i v-if="!object.icon" class="circular inverted user success icon"></i> <i v-if="!object.icon" class="circular inverted user success icon"></i>
<img alt="" class="ui big circular image" v-else v-lazy="$store.getters['instance/absoluteUrl'](object.icon.square_crop)" /> <img alt="" class="ui big circular image" v-else v-lazy="$store.getters['instance/absoluteUrl'](object.icon.urls.square_crop)" />
<div class="ellispsis content"> <div class="ellispsis content">
<div class="ui very small hidden divider"></div> <div class="ui very small hidden divider"></div>
<span>{{ displayName }}</span> <span>{{ displayName }}</span>
@ -49,7 +49,7 @@
@updated="$emit('updated', $event)" @updated="$emit('updated', $event)"
:content="object.summary" :content="object.summary"
:field-name="'summary'" :field-name="'summary'"
:update-url="`users/users/${$store.state.auth.username}/`" :update-url="`users/${$store.state.auth.username}/`"
:can-update="$store.state.auth.authenticated && object.full_username === $store.state.auth.fullUsername"></rendered-description> :can-update="$store.state.auth.authenticated && object.full_username === $store.state.auth.fullUsername"></rendered-description>
</div> </div>
</div> </div>

View File

@ -5,7 +5,7 @@
@updated="$emit('updated', $event)" @updated="$emit('updated', $event)"
:content="object.summary" :content="object.summary"
:field-name="'summary'" :field-name="'summary'"
:update-url="`users/users/${$store.state.auth.username}/`" :update-url="`users/${$store.state.auth.username}/`"
:can-update="$store.state.auth.authenticated && object.full_username === $store.state.auth.fullUsername"></rendered-description> :can-update="$store.state.auth.authenticated && object.full_username === $store.state.auth.fullUsername"></rendered-description>
<div class="ui hidden divider"></div> <div class="ui hidden divider"></div>
</div> </div>

View File

@ -9,7 +9,7 @@
<div class="seven wide column"> <div class="seven wide column">
<div class="ui two column grid"> <div class="ui two column grid">
<div class="column"> <div class="column">
<img alt="" class="huge channel-image" v-if="object.artist.cover" :src="$store.getters['instance/absoluteUrl'](object.artist.cover.medium_square_crop)"> <img alt="" class="huge channel-image" v-if="object.artist.cover" :src="$store.getters['instance/absoluteUrl'](object.artist.cover.urls.medium_square_crop)">
<i v-else class="huge circular inverted users violet icon"></i> <i v-else class="huge circular inverted users violet icon"></i>
</div> </div>
<div class="ui column right aligned"> <div class="ui column right aligned">

View File

@ -114,7 +114,7 @@ export default {
fetch () { fetch () {
let self = this let self = this
self.isLoading = true self.isLoading = true
axios.get('users/users/me/').then((response) => { axios.get('users/me/').then((response) => {
self.quotaStatus = response.data.quota_status self.quotaStatus = response.data.quota_status
self.isLoading = false self.isLoading = false
}) })

View File

@ -146,7 +146,7 @@ describe('store/auth', () => {
admin: true admin: true
} }
} }
moxios.stubRequest('users/users/me/', { moxios.stubRequest('users/me/', {
status: 200, status: 200,
response: profile response: profile
}) })