diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f3f787fecd58ec78d5937cca542cd0a3d6ef5679..a089db796581273303c53b1b9fec8ea973e2b700 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -203,7 +203,7 @@ pages: - cd docs - apt-get update - apt-get install -y graphviz - - pip install sphinx sphinx_rtd_theme + - pip install sphinx sphinx_rtd_theme django-environ django script: - ./build_docs.sh cache: diff --git a/api/config/settings/common.py b/api/config/settings/common.py index b55cfe84af1523220c548b024eadd5f4aa0a5d62..675d3e8ce17fe4095890cd3e43278b07fd8fdb33 100644 --- a/api/config/settings/common.py +++ b/api/config/settings/common.py @@ -1302,3 +1302,10 @@ PODCASTS_RSS_FEED_MAX_ITEMS = env.int("PODCASTS_RSS_FEED_MAX_ITEMS", default=250 """ Maximum number of RSS items to load in each podcast feed. """ + +IGNORE_FORWARDED_HOST_AND_PROTO = env.bool( + "IGNORE_FORWARDED_HOST_AND_PROTO", default=True +) +""" +Use :attr:`FUNKWHALE_HOSTNAME` and :attr:`FUNKWHALE_PROTOCOL ` instead of request header. +""" diff --git a/api/funkwhale_api/common/apps.py b/api/funkwhale_api/common/apps.py index cd671be291395b438ebd15a9caa42f53a81a51c6..7d94695a1f349c07d5f8e681af6c98ce42bc63e9 100644 --- a/api/funkwhale_api/common/apps.py +++ b/api/funkwhale_api/common/apps.py @@ -1,6 +1,7 @@ from django.apps import AppConfig, apps from . import mutations +from . import utils class CommonConfig(AppConfig): @@ -11,3 +12,4 @@ class CommonConfig(AppConfig): app_names = [app.name for app in apps.app_configs.values()] mutations.registry.autodiscover(app_names) + utils.monkey_patch_request_build_absolute_uri() diff --git a/api/funkwhale_api/common/utils.py b/api/funkwhale_api/common/utils.py index b721bdf7b24c4219c330719a7e4e8679f9c4f34e..0a38ef05f0ea79dd11dc7d91fa418cb94adfb1a0 100644 --- a/api/funkwhale_api/common/utils.py +++ b/api/funkwhale_api/common/utils.py @@ -1,6 +1,7 @@ import datetime from django.core.files.base import ContentFile +from django.http import request from django.utils.deconstruct import deconstructible import bleach.sanitizer @@ -433,3 +434,27 @@ def update_modification_date(obj, field="modification_date", date=None): obj.__class__.objects.filter(pk=obj.pk).update(**{field: date}) return date + + +def monkey_patch_request_build_absolute_uri(): + """ + Since we have FUNKWHALE_HOSTNAME and PROTOCOL hardcoded in settings, we can + override django's multisite logic which can break when reverse proxy aren't configured + properly. + """ + builtin_scheme = request.HttpRequest.scheme + + def scheme(self): + if settings.IGNORE_FORWARDED_HOST_AND_PROTO: + return settings.FUNKWHALE_PROTOCOL + return builtin_scheme.fget(self) + + builtin_get_host = request.HttpRequest.get_host + + def get_host(self): + if settings.IGNORE_FORWARDED_HOST_AND_PROTO: + return settings.FUNKWHALE_HOSTNAME + return builtin_get_host(self) + + request.HttpRequest.scheme = property(scheme) + request.HttpRequest.get_host = get_host diff --git a/api/funkwhale_api/manage/views.py b/api/funkwhale_api/manage/views.py index 0f0f16ce04815f03b071623d78be8aca358c25c5..adb7128e267334d66a8cc7231168a88c99e7ff96 100644 --- a/api/funkwhale_api/manage/views.py +++ b/api/funkwhale_api/manage/views.py @@ -84,8 +84,8 @@ class ManageArtistViewSet( music_models.Artist.objects.all() .order_by("-id") .select_related("attributed_to", "attachment_cover", "channel") - .annotate(_tracks_count=Count("tracks")) - .annotate(_albums_count=Count("albums")) + .annotate(_tracks_count=Count("tracks", distinct=True)) + .annotate(_albums_count=Count("albums", distinct=True)) .prefetch_related(music_views.TAG_PREFETCH) ) serializer_class = serializers.ManageArtistSerializer diff --git a/api/funkwhale_api/music/management/commands/fix_uploads.py b/api/funkwhale_api/music/management/commands/fix_uploads.py index 94f8dd21c44e213f2726f65dc708ca41e393c2ce..582a837c47096ed8c81c684072abdf5593e962ac 100644 --- a/api/funkwhale_api/music/management/commands/fix_uploads.py +++ b/api/funkwhale_api/music/management/commands/fix_uploads.py @@ -16,20 +16,44 @@ class Command(BaseCommand): default=False, help="Do not execute anything", ) + parser.add_argument( + "--mimetypes", + action="store_true", + dest="mimetypes", + default=True, + help="Check and fix mimetypes", + ) + parser.add_argument( + "--audio-data", + action="store_true", + dest="data", + default=False, + help="Check and fix bitrate and duration, can be really slow because it needs to access files", + ) + parser.add_argument( + "--size", + action="store_true", + dest="size", + default=False, + help="Check and fix file size, can be really slow because it needs to access files", + ) def handle(self, *args, **options): if options["dry_run"]: self.stdout.write("Dry-run on, will not commit anything") - self.fix_mimetypes(**options) - self.fix_file_data(**options) - self.fix_file_size(**options) + if options["mimetypes"]: + self.fix_mimetypes(**options) + if options["data"]: + self.fix_file_data(**options) + if options["size"]: + self.fix_file_size(**options) @transaction.atomic def fix_mimetypes(self, dry_run, **kwargs): self.stdout.write("Fixing missing mimetypes...") - matching = models.Upload.objects.filter(source__startswith="file://").exclude( - mimetype__startswith="audio/" - ) + matching = models.Upload.objects.filter( + Q(source__startswith="file://") | Q(source__startswith="upload://") + ).exclude(mimetype__startswith="audio/") self.stdout.write( "[mimetypes] {} entries found with bad or no mimetype".format( matching.count() diff --git a/api/funkwhale_api/music/utils.py b/api/funkwhale_api/music/utils.py index 14f245aaa4d8fca84ef2c6e04452990b9a2d64d3..64a7c24f85b4d1910e77a8279f4b9df4bc97aee6 100644 --- a/api/funkwhale_api/music/utils.py +++ b/api/funkwhale_api/music/utils.py @@ -22,6 +22,8 @@ def guess_mimetype(f): mt, _ = mimetypes.guess_type(f.name) if mt: t = mt + else: + t = EXTENSION_TO_MIMETYPE.get(f.name.split(".")[-1]) return t diff --git a/api/funkwhale_api/subsonic/serializers.py b/api/funkwhale_api/subsonic/serializers.py index fa6bc4475cb0d1887ac6688e201c49d6f4375aa3..d7042718af7194341c58e3b3bca78d0f80a5b8fc 100644 --- a/api/funkwhale_api/subsonic/serializers.py +++ b/api/funkwhale_api/subsonic/serializers.py @@ -130,7 +130,7 @@ def get_track_data(album, track, upload): data["bitrate"] = int(upload.bitrate / 1000) if upload.size: data["size"] = upload.size - if album.release_date: + if album and album.release_date: data["year"] = album.release_date.year else: data["year"] = track.creation_date.year diff --git a/api/tests/common/test_utils.py b/api/tests/common/test_utils.py index af2b25207413b2336527295652f8c55b3b102788..ffe9eae3b002771cb5516ae037005571f5d7b78c 100644 --- a/api/tests/common/test_utils.py +++ b/api/tests/common/test_utils.py @@ -197,3 +197,64 @@ def test_attach_file_content(factories, r_mock): assert new_attachment.file.read() == b"content" assert new_attachment.url is None assert new_attachment.mimetype == data["mimetype"] + + +@pytest.mark.parametrize( + "ignore, hostname, protocol, meta, path, expected", + [ + ( + False, + "test.hostname", + "http", + { + "HTTP_X_FORWARDED_HOST": "real.hostname", + "HTTP_X_FORWARDED_PROTO": "https", + }, + "/hello", + "https://real.hostname/hello", + ), + ( + False, + "test.hostname", + "http", + { + "HTTP_X_FORWARDED_HOST": "real.hostname", + "HTTP_X_FORWARDED_PROTO": "http", + }, + "/hello", + "http://real.hostname/hello", + ), + ( + True, + "test.hostname", + "http", + { + "HTTP_X_FORWARDED_HOST": "real.hostname", + "HTTP_X_FORWARDED_PROTO": "https", + }, + "/hello", + "http://test.hostname/hello", + ), + ( + True, + "test.hostname", + "https", + { + "HTTP_X_FORWARDED_HOST": "real.hostname", + "HTTP_X_FORWARDED_PROTO": "http", + }, + "/hello", + "https://test.hostname/hello", + ), + ], +) +def test_monkey_patch_request_build_absolute_uri( + ignore, hostname, protocol, meta, path, expected, fake_request, settings +): + settings.IGNORE_FORWARDED_HOST_AND_PROTO = ignore + settings.ALLOWED_HOSTS = "*" + settings.FUNKWHALE_HOSTNAME = hostname + settings.FUNKWHALE_PROTOCOL = protocol + request = fake_request.get("/", **meta) + + assert request.build_absolute_uri(path) == expected diff --git a/api/tests/music/test_views.py b/api/tests/music/test_views.py index db39f571f35c23bd5aaf3174f57468e3dc491143..3d79c0eba1d2a8e25a15d3052af297551f080926 100644 --- a/api/tests/music/test_views.py +++ b/api/tests/music/test_views.py @@ -824,6 +824,7 @@ def test_user_can_create_draft_upload( assert upload.source == "upload://test" assert upload.import_reference == "test" assert upload.import_status == "draft" + assert upload.mimetype == "audio/ogg" assert upload.track is None m.assert_not_called() diff --git a/changes/changelog.d/1082.bugfix b/changes/changelog.d/1082.bugfix new file mode 100644 index 0000000000000000000000000000000000000000..b99f0c1f0aa9c3984ee0cb4c4d18e5a3988663d3 --- /dev/null +++ b/changes/changelog.d/1082.bugfix @@ -0,0 +1 @@ +Fixed issue when displaying starred tracks on subsonic (#1082) diff --git a/changes/changelog.d/1085.enhancement b/changes/changelog.d/1085.enhancement new file mode 100644 index 0000000000000000000000000000000000000000..49cbee6f85eef04eba6d776fbe78186fdd72b423 --- /dev/null +++ b/changes/changelog.d/1085.enhancement @@ -0,0 +1 @@ +Make URL-building logic more resilient against reverse proxy misconfiguration (#1085) diff --git a/changes/changelog.d/1093.bugfix b/changes/changelog.d/1093.bugfix new file mode 100644 index 0000000000000000000000000000000000000000..1d9a15e59171bbd412da829645095cb6228a9c64 --- /dev/null +++ b/changes/changelog.d/1093.bugfix @@ -0,0 +1 @@ +Fixed mimetype detection issue that broke transcoding on some tracks (#1093). Run ``python manage.py fix_uploads --mimetypes`` to set proper mimetypes on existing uploads. diff --git a/changes/changelog.d/1096.bugfix b/changes/changelog.d/1096.bugfix new file mode 100644 index 0000000000000000000000000000000000000000..81ffb284d9a59783606b09c69baa4d00b2e11983 --- /dev/null +++ b/changes/changelog.d/1096.bugfix @@ -0,0 +1 @@ +Fixed wrong album and track count in admin artist API (#1096)