See #578: added opengraph and oembed data on artist / album / track urls
This commit is contained in:
parent
815d729367
commit
9220b2f0f1
|
@ -31,6 +31,7 @@ subsonic_router.register(r"subsonic/rest", SubsonicViewSet, base_name="subsonic"
|
||||||
|
|
||||||
|
|
||||||
v1_patterns += [
|
v1_patterns += [
|
||||||
|
url(r"^oembed/$", views.OembedView.as_view(), name="oembed"),
|
||||||
url(
|
url(
|
||||||
r"^instance/",
|
r"^instance/",
|
||||||
include(("funkwhale_api.instance.urls", "instance"), namespace="instance"),
|
include(("funkwhale_api.instance.urls", "instance"), namespace="instance"),
|
||||||
|
|
|
@ -70,7 +70,16 @@ else:
|
||||||
FUNKWHALE_PROTOCOL = _parsed.scheme
|
FUNKWHALE_PROTOCOL = _parsed.scheme
|
||||||
|
|
||||||
FUNKWHALE_URL = "{}://{}".format(FUNKWHALE_PROTOCOL, FUNKWHALE_HOSTNAME)
|
FUNKWHALE_URL = "{}://{}".format(FUNKWHALE_PROTOCOL, FUNKWHALE_HOSTNAME)
|
||||||
|
FUNKWHALE_SPA_HTML_ROOT = env(
|
||||||
|
"FUNKWHALE_SPA_HTML_ROOT", default=FUNKWHALE_URL + "/front/"
|
||||||
|
)
|
||||||
|
FUNKWHALE_SPA_HTML_CACHE_DURATION = env.int(
|
||||||
|
"FUNKWHALE_SPA_HTML_CACHE_DURATION", default=60 * 15
|
||||||
|
)
|
||||||
|
FUNKWHALE_EMBED_URL = env(
|
||||||
|
"FUNKWHALE_EMBED_URL", default=FUNKWHALE_SPA_HTML_ROOT + "embed.html"
|
||||||
|
)
|
||||||
|
APP_NAME = "Funkwhale"
|
||||||
|
|
||||||
# XXX: deprecated, see #186
|
# XXX: deprecated, see #186
|
||||||
FEDERATION_ENABLED = env.bool("FEDERATION_ENABLED", default=True)
|
FEDERATION_ENABLED = env.bool("FEDERATION_ENABLED", default=True)
|
||||||
|
@ -159,7 +168,7 @@ INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
|
||||||
# MIDDLEWARE CONFIGURATION
|
# MIDDLEWARE CONFIGURATION
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
MIDDLEWARE = (
|
MIDDLEWARE = (
|
||||||
# Make sure djangosecure.middleware.SecurityMiddleware is listed first
|
"funkwhale_api.common.middleware.SPAFallbackMiddleware",
|
||||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
"corsheaders.middleware.CorsMiddleware",
|
"corsheaders.middleware.CorsMiddleware",
|
||||||
"django.middleware.common.CommonMiddleware",
|
"django.middleware.common.CommonMiddleware",
|
||||||
|
@ -305,6 +314,7 @@ FILE_UPLOAD_PERMISSIONS = 0o644
|
||||||
# URL Configuration
|
# URL Configuration
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
ROOT_URLCONF = "config.urls"
|
ROOT_URLCONF = "config.urls"
|
||||||
|
SPA_URLCONF = "config.spa_urls"
|
||||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#wsgi-application
|
# See: https://docs.djangoproject.com/en/dev/ref/settings/#wsgi-application
|
||||||
WSGI_APPLICATION = "config.wsgi.application"
|
WSGI_APPLICATION = "config.wsgi.application"
|
||||||
ASGI_APPLICATION = "config.routing.application"
|
ASGI_APPLICATION = "config.routing.application"
|
||||||
|
@ -400,7 +410,13 @@ if AUTH_LDAP_ENABLED:
|
||||||
AUTOSLUG_SLUGIFY_FUNCTION = "slugify.slugify"
|
AUTOSLUG_SLUGIFY_FUNCTION = "slugify.slugify"
|
||||||
|
|
||||||
CACHE_DEFAULT = "redis://127.0.0.1:6379/0"
|
CACHE_DEFAULT = "redis://127.0.0.1:6379/0"
|
||||||
CACHES = {"default": env.cache_url("CACHE_URL", default=CACHE_DEFAULT)}
|
CACHES = {
|
||||||
|
"default": env.cache_url("CACHE_URL", default=CACHE_DEFAULT),
|
||||||
|
"local": {
|
||||||
|
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
|
||||||
|
"LOCATION": "local-cache",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
CACHES["default"]["BACKEND"] = "django_redis.cache.RedisCache"
|
CACHES["default"]["BACKEND"] = "django_redis.cache.RedisCache"
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
from django import urls
|
||||||
|
|
||||||
|
from funkwhale_api.music import spa_views
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
urls.re_path(
|
||||||
|
r"^library/tracks/(?P<pk>\d+)/?$", spa_views.library_track, name="library_track"
|
||||||
|
),
|
||||||
|
urls.re_path(
|
||||||
|
r"^library/albums/(?P<pk>\d+)/?$", spa_views.library_album, name="library_album"
|
||||||
|
),
|
||||||
|
urls.re_path(
|
||||||
|
r"^library/artists/(?P<pk>\d+)/?$",
|
||||||
|
spa_views.library_artist,
|
||||||
|
name="library_artist",
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,137 @@
|
||||||
|
import html
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from django import http
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.cache import caches
|
||||||
|
from django import urls
|
||||||
|
|
||||||
|
from . import preferences
|
||||||
|
from . import utils
|
||||||
|
|
||||||
|
EXCLUDED_PATHS = ["/api", "/federation", "/.well-known"]
|
||||||
|
|
||||||
|
|
||||||
|
def should_fallback_to_spa(path):
|
||||||
|
if path == "/":
|
||||||
|
return True
|
||||||
|
return not any([path.startswith(m) for m in EXCLUDED_PATHS])
|
||||||
|
|
||||||
|
|
||||||
|
def serve_spa(request):
|
||||||
|
html = get_spa_html(settings.FUNKWHALE_SPA_HTML_ROOT)
|
||||||
|
head, tail = html.split("</head>", 1)
|
||||||
|
if not preferences.get("common__api_authentication_required"):
|
||||||
|
try:
|
||||||
|
request_tags = get_request_head_tags(request) or []
|
||||||
|
except urls.exceptions.Resolver404:
|
||||||
|
# we don't have any custom tags for this route
|
||||||
|
request_tags = []
|
||||||
|
else:
|
||||||
|
# API is not open, we don't expose any custom data
|
||||||
|
request_tags = []
|
||||||
|
default_tags = get_default_head_tags(request.path)
|
||||||
|
unique_attributes = ["name", "property"]
|
||||||
|
|
||||||
|
final_tags = request_tags
|
||||||
|
skip = []
|
||||||
|
|
||||||
|
for t in final_tags:
|
||||||
|
for attr in unique_attributes:
|
||||||
|
if attr in t:
|
||||||
|
skip.append(t[attr])
|
||||||
|
for t in default_tags:
|
||||||
|
existing = False
|
||||||
|
for attr in unique_attributes:
|
||||||
|
if t.get(attr) in skip:
|
||||||
|
existing = True
|
||||||
|
break
|
||||||
|
if not existing:
|
||||||
|
final_tags.append(t)
|
||||||
|
|
||||||
|
# let's inject our meta tags in the HTML
|
||||||
|
head += "\n" + "\n".join(render_tags(final_tags)) + "\n</head>"
|
||||||
|
|
||||||
|
return http.HttpResponse(head + tail)
|
||||||
|
|
||||||
|
|
||||||
|
def get_spa_html(spa_url):
|
||||||
|
cache_key = "spa-html:{}".format(spa_url)
|
||||||
|
cached = caches["local"].get(cache_key)
|
||||||
|
if cached:
|
||||||
|
return cached
|
||||||
|
|
||||||
|
response = requests.get(
|
||||||
|
utils.join_url(spa_url, "index.html"),
|
||||||
|
verify=settings.EXTERNAL_REQUESTS_VERIFY_SSL,
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
content = response.text
|
||||||
|
caches["local"].set(cache_key, content, settings.FUNKWHALE_SPA_HTML_CACHE_DURATION)
|
||||||
|
return content
|
||||||
|
|
||||||
|
|
||||||
|
def get_default_head_tags(path):
|
||||||
|
instance_name = preferences.get("instance__name")
|
||||||
|
short_description = preferences.get("instance__short_description")
|
||||||
|
app_name = settings.APP_NAME
|
||||||
|
|
||||||
|
parts = [instance_name, app_name]
|
||||||
|
|
||||||
|
return [
|
||||||
|
{"tag": "meta", "property": "og:type", "content": "website"},
|
||||||
|
{
|
||||||
|
"tag": "meta",
|
||||||
|
"property": "og:site_name",
|
||||||
|
"content": " - ".join([p for p in parts if p]),
|
||||||
|
},
|
||||||
|
{"tag": "meta", "property": "og:description", "content": short_description},
|
||||||
|
{
|
||||||
|
"tag": "meta",
|
||||||
|
"property": "og:image",
|
||||||
|
"content": utils.join_url(settings.FUNKWHALE_URL, "/front/favicon.png"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "meta",
|
||||||
|
"property": "og:url",
|
||||||
|
"content": utils.join_url(settings.FUNKWHALE_URL, path),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def render_tags(tags):
|
||||||
|
"""
|
||||||
|
Given a dict like {'tag': 'meta', 'hello': 'world'}
|
||||||
|
return a html ready tag like
|
||||||
|
<meta hello="world" />
|
||||||
|
"""
|
||||||
|
for tag in tags:
|
||||||
|
|
||||||
|
yield "<{tag} {attrs} />".format(
|
||||||
|
tag=tag.pop("tag"),
|
||||||
|
attrs=" ".join(
|
||||||
|
[
|
||||||
|
'{}="{}"'.format(a, html.escape(str(v)))
|
||||||
|
for a, v in sorted(tag.items())
|
||||||
|
if v
|
||||||
|
]
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_request_head_tags(request):
|
||||||
|
match = urls.resolve(request.path, urlconf=settings.SPA_URLCONF)
|
||||||
|
return match.func(request, *match.args, **match.kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class SPAFallbackMiddleware:
|
||||||
|
def __init__(self, get_response):
|
||||||
|
self.get_response = get_response
|
||||||
|
|
||||||
|
def __call__(self, request):
|
||||||
|
response = self.get_response(request)
|
||||||
|
|
||||||
|
if response.status_code == 404 and should_fallback_to_spa(request.path):
|
||||||
|
return serve_spa(request)
|
||||||
|
|
||||||
|
return response
|
|
@ -3,9 +3,12 @@ from django.utils.deconstruct import deconstructible
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import uuid
|
import uuid
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
from urllib.parse import parse_qs, urlencode, urlsplit, urlunsplit
|
from urllib.parse import parse_qs, urlencode, urlsplit, urlunsplit
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django import urls
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
|
||||||
|
|
||||||
|
@ -107,3 +110,32 @@ def chunk_queryset(source_qs, chunk_size):
|
||||||
|
|
||||||
if nb_items < chunk_size:
|
if nb_items < chunk_size:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def join_url(start, end):
|
||||||
|
if start.endswith("/") and end.startswith("/"):
|
||||||
|
return start + end[1:]
|
||||||
|
|
||||||
|
if not start.endswith("/") and not end.startswith("/"):
|
||||||
|
return start + "/" + end
|
||||||
|
|
||||||
|
return start + end
|
||||||
|
|
||||||
|
|
||||||
|
def spa_reverse(name, args=[], kwargs={}):
|
||||||
|
return urls.reverse(name, urlconf=settings.SPA_URLCONF, args=args, kwargs=kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def spa_resolve(path):
|
||||||
|
return urls.resolve(path, urlconf=settings.SPA_URLCONF)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_meta(html):
|
||||||
|
# dirty but this is only for testing so we don't really care,
|
||||||
|
# we convert the html string to xml so it can be parsed as xml
|
||||||
|
html = '<?xml version="1.0"?>' + html
|
||||||
|
tree = ET.fromstring(html)
|
||||||
|
|
||||||
|
meta = [elem for elem in tree.iter() if elem.tag in ["meta", "link"]]
|
||||||
|
|
||||||
|
return [dict([("tag", elem.tag)] + list(elem.items())) for elem in meta]
|
||||||
|
|
|
@ -1,12 +1,18 @@
|
||||||
|
import urllib.parse
|
||||||
|
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
from django import urls
|
||||||
|
from django.conf import settings
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from taggit.models import Tag
|
from taggit.models import Tag
|
||||||
from versatileimagefield.serializers import VersatileImageFieldSerializer
|
from versatileimagefield.serializers import VersatileImageFieldSerializer
|
||||||
|
|
||||||
from funkwhale_api.activity import serializers as activity_serializers
|
from funkwhale_api.activity import serializers as activity_serializers
|
||||||
from funkwhale_api.common import serializers as common_serializers
|
from funkwhale_api.common import serializers as common_serializers
|
||||||
|
from funkwhale_api.common import preferences
|
||||||
from funkwhale_api.common import utils as common_utils
|
from funkwhale_api.common import utils as common_utils
|
||||||
from funkwhale_api.federation import routes
|
from funkwhale_api.federation import routes
|
||||||
|
from funkwhale_api.federation import utils as federation_utils
|
||||||
|
|
||||||
from . import filters, models, tasks
|
from . import filters, models, tasks
|
||||||
|
|
||||||
|
@ -380,3 +386,98 @@ class TrackActivitySerializer(activity_serializers.ModelSerializer):
|
||||||
|
|
||||||
def get_type(self, obj):
|
def get_type(self, obj):
|
||||||
return "Audio"
|
return "Audio"
|
||||||
|
|
||||||
|
|
||||||
|
class OembedSerializer(serializers.Serializer):
|
||||||
|
format = serializers.ChoiceField(choices=["json"])
|
||||||
|
url = serializers.URLField()
|
||||||
|
maxheight = serializers.IntegerField(required=False)
|
||||||
|
maxwidth = serializers.IntegerField(required=False)
|
||||||
|
|
||||||
|
def validate(self, validated_data):
|
||||||
|
try:
|
||||||
|
match = common_utils.spa_resolve(
|
||||||
|
urllib.parse.urlparse(validated_data["url"]).path
|
||||||
|
)
|
||||||
|
except urls.exceptions.Resolver404:
|
||||||
|
raise serializers.ValidationError(
|
||||||
|
"Invalid URL {}".format(validated_data["url"])
|
||||||
|
)
|
||||||
|
data = {
|
||||||
|
"version": 1.0,
|
||||||
|
"type": "rich",
|
||||||
|
"provider_name": "{} - {}".format(
|
||||||
|
preferences.get("instance__name"), settings.APP_NAME
|
||||||
|
),
|
||||||
|
"provider_url": settings.FUNKWHALE_URL,
|
||||||
|
"height": validated_data.get("maxheight") or 400,
|
||||||
|
"width": validated_data.get("maxwidth") or 600,
|
||||||
|
}
|
||||||
|
embed_id = None
|
||||||
|
embed_type = None
|
||||||
|
if match.url_name == "library_track":
|
||||||
|
qs = models.Track.objects.select_related("artist", "album__artist").filter(
|
||||||
|
pk=int(match.kwargs["pk"])
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
track = qs.get()
|
||||||
|
except models.Track.DoesNotExist:
|
||||||
|
raise serializers.ValidationError(
|
||||||
|
"No track matching id {}".format(match.kwargs["pk"])
|
||||||
|
)
|
||||||
|
embed_type = "track"
|
||||||
|
embed_id = track.pk
|
||||||
|
data["title"] = "{} by {}".format(track.title, track.artist.name)
|
||||||
|
if track.album.cover:
|
||||||
|
data["thumbnail_url"] = federation_utils.full_url(
|
||||||
|
track.album.cover.crop["400x400"].url
|
||||||
|
)
|
||||||
|
data["description"] = track.full_name
|
||||||
|
data["author_name"] = track.artist.name
|
||||||
|
data["height"] = 150
|
||||||
|
data["author_url"] = federation_utils.full_url(
|
||||||
|
common_utils.spa_reverse(
|
||||||
|
"library_artist", kwargs={"pk": track.artist.pk}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif match.url_name == "library_album":
|
||||||
|
qs = models.Album.objects.select_related("artist").filter(
|
||||||
|
pk=int(match.kwargs["pk"])
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
album = qs.get()
|
||||||
|
except models.Album.DoesNotExist:
|
||||||
|
raise serializers.ValidationError(
|
||||||
|
"No album matching id {}".format(match.kwargs["pk"])
|
||||||
|
)
|
||||||
|
embed_type = "album"
|
||||||
|
embed_id = album.pk
|
||||||
|
if album.cover:
|
||||||
|
data["thumbnail_url"] = federation_utils.full_url(
|
||||||
|
album.cover.crop["400x400"].url
|
||||||
|
)
|
||||||
|
data["title"] = "{} by {}".format(album.title, album.artist.name)
|
||||||
|
data["description"] = "{} by {}".format(album.title, album.artist.name)
|
||||||
|
data["author_name"] = album.artist.name
|
||||||
|
data["height"] = 400
|
||||||
|
data["author_url"] = federation_utils.full_url(
|
||||||
|
common_utils.spa_reverse(
|
||||||
|
"library_artist", kwargs={"pk": album.artist.pk}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise serializers.ValidationError(
|
||||||
|
"Unsupported url: {}".format(validated_data["url"])
|
||||||
|
)
|
||||||
|
data[
|
||||||
|
"html"
|
||||||
|
] = '<iframe width="{}" height="{}" scrolling="no" frameborder="no" src="{}"></iframe>'.format(
|
||||||
|
data["width"],
|
||||||
|
data["height"],
|
||||||
|
settings.FUNKWHALE_EMBED_URL
|
||||||
|
+ "?type={}&id={}".format(embed_type, embed_id),
|
||||||
|
)
|
||||||
|
return data
|
||||||
|
|
||||||
|
def create(self, data):
|
||||||
|
return data
|
||||||
|
|
|
@ -0,0 +1,161 @@
|
||||||
|
from django.conf import settings
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from funkwhale_api.common import utils
|
||||||
|
|
||||||
|
from . import models
|
||||||
|
|
||||||
|
|
||||||
|
def library_track(request, pk):
|
||||||
|
queryset = models.Track.objects.filter(pk=pk).select_related("album", "artist")
|
||||||
|
try:
|
||||||
|
obj = queryset.get()
|
||||||
|
except models.Track.DoesNotExist:
|
||||||
|
return []
|
||||||
|
track_url = utils.join_url(
|
||||||
|
settings.FUNKWHALE_URL,
|
||||||
|
utils.spa_reverse("library_track", kwargs={"pk": obj.pk}),
|
||||||
|
)
|
||||||
|
metas = [
|
||||||
|
{"tag": "meta", "property": "og:url", "content": track_url},
|
||||||
|
{"tag": "meta", "property": "og:title", "content": obj.title},
|
||||||
|
{"tag": "meta", "property": "og:type", "content": "music.song"},
|
||||||
|
{"tag": "meta", "property": "music:album:disc", "content": obj.disc_number},
|
||||||
|
{"tag": "meta", "property": "music:album:track", "content": obj.position},
|
||||||
|
{
|
||||||
|
"tag": "meta",
|
||||||
|
"property": "music:musician",
|
||||||
|
"content": utils.join_url(
|
||||||
|
settings.FUNKWHALE_URL,
|
||||||
|
utils.spa_reverse("library_artist", kwargs={"pk": obj.artist.pk}),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "meta",
|
||||||
|
"property": "music:album",
|
||||||
|
"content": utils.join_url(
|
||||||
|
settings.FUNKWHALE_URL,
|
||||||
|
utils.spa_reverse("library_album", kwargs={"pk": obj.album.pk}),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
if obj.album.cover:
|
||||||
|
metas.append(
|
||||||
|
{
|
||||||
|
"tag": "meta",
|
||||||
|
"property": "og:image",
|
||||||
|
"content": utils.join_url(settings.FUNKWHALE_URL, obj.album.cover.url),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if obj.uploads.playable_by(None).exists():
|
||||||
|
metas.append(
|
||||||
|
{
|
||||||
|
"tag": "meta",
|
||||||
|
"property": "og:audio",
|
||||||
|
"content": utils.join_url(settings.FUNKWHALE_URL, obj.listen_url),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
metas.append(
|
||||||
|
{
|
||||||
|
"tag": "link",
|
||||||
|
"rel": "alternate",
|
||||||
|
"type": "application/json+oembed",
|
||||||
|
"href": (
|
||||||
|
utils.join_url(settings.FUNKWHALE_URL, reverse("api:v1:oembed"))
|
||||||
|
+ "?url={}".format(track_url)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return metas
|
||||||
|
|
||||||
|
|
||||||
|
def library_album(request, pk):
|
||||||
|
queryset = models.Album.objects.filter(pk=pk).select_related("artist")
|
||||||
|
try:
|
||||||
|
obj = queryset.get()
|
||||||
|
except models.Album.DoesNotExist:
|
||||||
|
return []
|
||||||
|
album_url = utils.join_url(
|
||||||
|
settings.FUNKWHALE_URL,
|
||||||
|
utils.spa_reverse("library_album", kwargs={"pk": obj.pk}),
|
||||||
|
)
|
||||||
|
metas = [
|
||||||
|
{"tag": "meta", "property": "og:url", "content": album_url},
|
||||||
|
{"tag": "meta", "property": "og:title", "content": obj.title},
|
||||||
|
{"tag": "meta", "property": "og:type", "content": "music.album"},
|
||||||
|
{
|
||||||
|
"tag": "meta",
|
||||||
|
"property": "music:musician",
|
||||||
|
"content": utils.join_url(
|
||||||
|
settings.FUNKWHALE_URL,
|
||||||
|
utils.spa_reverse("library_artist", kwargs={"pk": obj.artist.pk}),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
if obj.release_date:
|
||||||
|
metas.append(
|
||||||
|
{
|
||||||
|
"tag": "meta",
|
||||||
|
"property": "music:release_date",
|
||||||
|
"content": str(obj.release_date),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if obj.cover:
|
||||||
|
metas.append(
|
||||||
|
{
|
||||||
|
"tag": "meta",
|
||||||
|
"property": "og:image",
|
||||||
|
"content": utils.join_url(settings.FUNKWHALE_URL, obj.cover.url),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if models.Upload.objects.filter(track__album=obj).playable_by(None).exists():
|
||||||
|
metas.append(
|
||||||
|
{
|
||||||
|
"tag": "link",
|
||||||
|
"rel": "alternate",
|
||||||
|
"type": "application/json+oembed",
|
||||||
|
"href": (
|
||||||
|
utils.join_url(settings.FUNKWHALE_URL, reverse("api:v1:oembed"))
|
||||||
|
+ "?url={}".format(album_url)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return metas
|
||||||
|
|
||||||
|
|
||||||
|
def library_artist(request, pk):
|
||||||
|
queryset = models.Artist.objects.filter(pk=pk)
|
||||||
|
try:
|
||||||
|
obj = queryset.get()
|
||||||
|
except models.Artist.DoesNotExist:
|
||||||
|
return []
|
||||||
|
artist_url = utils.join_url(
|
||||||
|
settings.FUNKWHALE_URL,
|
||||||
|
utils.spa_reverse("library_artist", kwargs={"pk": obj.pk}),
|
||||||
|
)
|
||||||
|
# we use latest album's cover as artist image
|
||||||
|
latest_album = (
|
||||||
|
obj.albums.exclude(cover="").exclude(cover=None).order_by("release_date").last()
|
||||||
|
)
|
||||||
|
metas = [
|
||||||
|
{"tag": "meta", "property": "og:url", "content": artist_url},
|
||||||
|
{"tag": "meta", "property": "og:title", "content": obj.name},
|
||||||
|
{"tag": "meta", "property": "og:type", "content": "profile"},
|
||||||
|
]
|
||||||
|
|
||||||
|
if latest_album and latest_album.cover:
|
||||||
|
metas.append(
|
||||||
|
{
|
||||||
|
"tag": "meta",
|
||||||
|
"property": "og:image",
|
||||||
|
"content": utils.join_url(
|
||||||
|
settings.FUNKWHALE_URL, latest_album.cover.url
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return metas
|
|
@ -508,3 +508,13 @@ class LicenseViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
first_arg = [i.conf for i in instance_or_qs if i.conf]
|
first_arg = [i.conf for i in instance_or_qs if i.conf]
|
||||||
return super().get_serializer(*((first_arg,) + args[1:]), **kwargs)
|
return super().get_serializer(*((first_arg,) + args[1:]), **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class OembedView(views.APIView):
|
||||||
|
permission_classes = [common_permissions.ConditionalAuthentication]
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
serializer = serializers.OembedSerializer(data=request.GET)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
embed_data = serializer.save()
|
||||||
|
return Response(embed_data)
|
||||||
|
|
|
@ -0,0 +1,137 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from funkwhale_api.common import middleware
|
||||||
|
|
||||||
|
|
||||||
|
def test_spa_fallback_middleware_no_404(mocker):
|
||||||
|
get_response = mocker.Mock()
|
||||||
|
get_response.return_value = mocker.Mock(status_code=200)
|
||||||
|
request = mocker.Mock(path="/")
|
||||||
|
m = middleware.SPAFallbackMiddleware(get_response)
|
||||||
|
|
||||||
|
assert m(request) == get_response.return_value
|
||||||
|
|
||||||
|
|
||||||
|
def test_spa_middleware_calls_should_fallback_false(mocker):
|
||||||
|
get_response = mocker.Mock()
|
||||||
|
get_response.return_value = mocker.Mock(status_code=404)
|
||||||
|
should_falback = mocker.patch.object(
|
||||||
|
middleware, "should_fallback_to_spa", return_value=False
|
||||||
|
)
|
||||||
|
request = mocker.Mock(path="/")
|
||||||
|
|
||||||
|
m = middleware.SPAFallbackMiddleware(get_response)
|
||||||
|
|
||||||
|
assert m(request) == get_response.return_value
|
||||||
|
should_falback.assert_called_once_with(request.path)
|
||||||
|
|
||||||
|
|
||||||
|
def test_spa_middleware_should_fallback_true(mocker):
|
||||||
|
get_response = mocker.Mock()
|
||||||
|
get_response.return_value = mocker.Mock(status_code=404)
|
||||||
|
request = mocker.Mock(path="/")
|
||||||
|
mocker.patch.object(middleware, "should_fallback_to_spa", return_value=True)
|
||||||
|
serve_spa = mocker.patch.object(middleware, "serve_spa")
|
||||||
|
m = middleware.SPAFallbackMiddleware(get_response)
|
||||||
|
|
||||||
|
assert m(request) == serve_spa.return_value
|
||||||
|
serve_spa.assert_called_once_with(request)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"path,expected",
|
||||||
|
[("/", True), ("/federation", False), ("/api", False), ("/an/spa/path/", True)],
|
||||||
|
)
|
||||||
|
def test_should_fallback(path, expected, mocker):
|
||||||
|
assert middleware.should_fallback_to_spa(path) is expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_serve_spa_from_cache(mocker, settings, preferences, no_api_auth):
|
||||||
|
|
||||||
|
request = mocker.Mock(path="/")
|
||||||
|
get_spa_html = mocker.patch.object(
|
||||||
|
middleware, "get_spa_html", return_value="<html><head></head></html>"
|
||||||
|
)
|
||||||
|
mocker.patch.object(
|
||||||
|
middleware,
|
||||||
|
"get_default_head_tags",
|
||||||
|
return_value=[
|
||||||
|
{"tag": "meta", "property": "og:title", "content": "default title"},
|
||||||
|
{"tag": "meta", "property": "og:site_name", "content": "default site name"},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
get_request_head_tags = mocker.patch.object(
|
||||||
|
middleware,
|
||||||
|
"get_request_head_tags",
|
||||||
|
return_value=[
|
||||||
|
{"tag": "meta", "property": "og:title", "content": "custom title"},
|
||||||
|
{
|
||||||
|
"tag": "meta",
|
||||||
|
"property": "og:description",
|
||||||
|
"content": "custom description",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
response = middleware.serve_spa(request)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
expected = [
|
||||||
|
"<html><head>",
|
||||||
|
'<meta content="custom title" property="og:title" />',
|
||||||
|
'<meta content="custom description" property="og:description" />',
|
||||||
|
'<meta content="default site name" property="og:site_name" />',
|
||||||
|
"</head></html>",
|
||||||
|
]
|
||||||
|
get_spa_html.assert_called_once_with(settings.FUNKWHALE_SPA_HTML_ROOT)
|
||||||
|
get_request_head_tags.assert_called_once_with(request)
|
||||||
|
assert response.content == "\n".join(expected).encode()
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_default_head_tags(preferences, settings):
|
||||||
|
settings.APP_NAME = "Funkwhale"
|
||||||
|
preferences["instance__name"] = "Hello"
|
||||||
|
preferences["instance__short_description"] = "World"
|
||||||
|
|
||||||
|
expected = [
|
||||||
|
{"tag": "meta", "property": "og:type", "content": "website"},
|
||||||
|
{"tag": "meta", "property": "og:site_name", "content": "Hello - Funkwhale"},
|
||||||
|
{"tag": "meta", "property": "og:description", "content": "World"},
|
||||||
|
{
|
||||||
|
"tag": "meta",
|
||||||
|
"property": "og:image",
|
||||||
|
"content": settings.FUNKWHALE_URL + "/front/favicon.png",
|
||||||
|
},
|
||||||
|
{"tag": "meta", "property": "og:url", "content": settings.FUNKWHALE_URL + "/"},
|
||||||
|
]
|
||||||
|
|
||||||
|
assert middleware.get_default_head_tags("/") == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_spa_html_from_cache(local_cache):
|
||||||
|
local_cache.set("spa-html:http://test", "hello world")
|
||||||
|
|
||||||
|
assert middleware.get_spa_html("http://test") == "hello world"
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_spa_html_from_http(local_cache, r_mock, mocker, settings):
|
||||||
|
cache_set = mocker.spy(local_cache, "set")
|
||||||
|
url = "http://test"
|
||||||
|
r_mock.get(url + "/index.html", text="hello world")
|
||||||
|
|
||||||
|
assert middleware.get_spa_html("http://test") == "hello world"
|
||||||
|
cache_set.assert_called_once_with(
|
||||||
|
"spa-html:{}".format(url),
|
||||||
|
"hello world",
|
||||||
|
settings.FUNKWHALE_SPA_HTML_CACHE_DURATION,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_route_head_tags(mocker, settings):
|
||||||
|
match = mocker.Mock(args=[], kwargs={"pk": 42}, func=mocker.Mock())
|
||||||
|
resolve = mocker.patch("django.urls.resolve", return_value=match)
|
||||||
|
request = mocker.Mock(path="/tracks/42")
|
||||||
|
tags = middleware.get_request_head_tags(request)
|
||||||
|
|
||||||
|
assert tags == match.func.return_value
|
||||||
|
match.func.assert_called_once_with(request, *[], **{"pk": 42})
|
||||||
|
resolve.assert_called_once_with(request.path, urlconf=settings.SPA_URLCONF)
|
|
@ -13,7 +13,7 @@ import factory
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from django.contrib.auth.models import AnonymousUser
|
from django.contrib.auth.models import AnonymousUser
|
||||||
from django.core.cache import cache as django_cache
|
from django.core.cache import cache as django_cache, caches
|
||||||
from django.core.files import uploadedfile
|
from django.core.files import uploadedfile
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.test import client
|
from django.test import client
|
||||||
|
@ -100,6 +100,12 @@ def cache():
|
||||||
django_cache.clear()
|
django_cache.clear()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def local_cache():
|
||||||
|
yield caches["local"]
|
||||||
|
caches["local"].clear()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def factories(db):
|
def factories(db):
|
||||||
"""
|
"""
|
||||||
|
@ -382,3 +388,15 @@ def temp_signal(mocker):
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def stdout():
|
def stdout():
|
||||||
yield io.StringIO()
|
yield io.StringIO()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def spa_html(r_mock, settings):
|
||||||
|
yield r_mock.get(
|
||||||
|
settings.FUNKWHALE_SPA_HTML_ROOT + "index.html", text="<head></head>"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def no_api_auth(preferences):
|
||||||
|
preferences["common__api_authentication_required"] = False
|
||||||
|
|
|
@ -0,0 +1,148 @@
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from funkwhale_api.common import utils
|
||||||
|
|
||||||
|
|
||||||
|
def test_library_track(spa_html, no_api_auth, client, factories, settings):
|
||||||
|
track = factories["music.Upload"](playable=True, track__disc_number=1).track
|
||||||
|
url = "/library/tracks/{}".format(track.pk)
|
||||||
|
|
||||||
|
response = client.get(url)
|
||||||
|
|
||||||
|
expected_metas = [
|
||||||
|
{
|
||||||
|
"tag": "meta",
|
||||||
|
"property": "og:url",
|
||||||
|
"content": utils.join_url(settings.FUNKWHALE_URL, url),
|
||||||
|
},
|
||||||
|
{"tag": "meta", "property": "og:title", "content": track.title},
|
||||||
|
{"tag": "meta", "property": "og:type", "content": "music.song"},
|
||||||
|
{
|
||||||
|
"tag": "meta",
|
||||||
|
"property": "music:album:disc",
|
||||||
|
"content": str(track.disc_number),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "meta",
|
||||||
|
"property": "music:album:track",
|
||||||
|
"content": str(track.position),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "meta",
|
||||||
|
"property": "music:musician",
|
||||||
|
"content": utils.join_url(
|
||||||
|
settings.FUNKWHALE_URL,
|
||||||
|
utils.spa_reverse("library_artist", kwargs={"pk": track.artist.pk}),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "meta",
|
||||||
|
"property": "music:album",
|
||||||
|
"content": utils.join_url(
|
||||||
|
settings.FUNKWHALE_URL,
|
||||||
|
utils.spa_reverse("library_album", kwargs={"pk": track.album.pk}),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "meta",
|
||||||
|
"property": "og:image",
|
||||||
|
"content": utils.join_url(settings.FUNKWHALE_URL, track.album.cover.url),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "meta",
|
||||||
|
"property": "og:audio",
|
||||||
|
"content": utils.join_url(settings.FUNKWHALE_URL, track.listen_url),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "link",
|
||||||
|
"rel": "alternate",
|
||||||
|
"type": "application/json+oembed",
|
||||||
|
"href": (
|
||||||
|
utils.join_url(settings.FUNKWHALE_URL, reverse("api:v1:oembed"))
|
||||||
|
+ "?url={}".format(utils.join_url(settings.FUNKWHALE_URL, url))
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
metas = utils.parse_meta(response.content.decode())
|
||||||
|
|
||||||
|
# we only test our custom metas, not the default ones
|
||||||
|
assert metas[: len(expected_metas)] == expected_metas
|
||||||
|
|
||||||
|
|
||||||
|
def test_library_album(spa_html, no_api_auth, client, factories, settings):
|
||||||
|
track = factories["music.Upload"](playable=True, track__disc_number=1).track
|
||||||
|
album = track.album
|
||||||
|
url = "/library/albums/{}".format(album.pk)
|
||||||
|
|
||||||
|
response = client.get(url)
|
||||||
|
|
||||||
|
expected_metas = [
|
||||||
|
{
|
||||||
|
"tag": "meta",
|
||||||
|
"property": "og:url",
|
||||||
|
"content": utils.join_url(settings.FUNKWHALE_URL, url),
|
||||||
|
},
|
||||||
|
{"tag": "meta", "property": "og:title", "content": album.title},
|
||||||
|
{"tag": "meta", "property": "og:type", "content": "music.album"},
|
||||||
|
{
|
||||||
|
"tag": "meta",
|
||||||
|
"property": "music:musician",
|
||||||
|
"content": utils.join_url(
|
||||||
|
settings.FUNKWHALE_URL,
|
||||||
|
utils.spa_reverse("library_artist", kwargs={"pk": album.artist.pk}),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "meta",
|
||||||
|
"property": "music:release_date",
|
||||||
|
"content": str(album.release_date),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "meta",
|
||||||
|
"property": "og:image",
|
||||||
|
"content": utils.join_url(settings.FUNKWHALE_URL, album.cover.url),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "link",
|
||||||
|
"rel": "alternate",
|
||||||
|
"type": "application/json+oembed",
|
||||||
|
"href": (
|
||||||
|
utils.join_url(settings.FUNKWHALE_URL, reverse("api:v1:oembed"))
|
||||||
|
+ "?url={}".format(utils.join_url(settings.FUNKWHALE_URL, url))
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
metas = utils.parse_meta(response.content.decode())
|
||||||
|
|
||||||
|
# we only test our custom metas, not the default ones
|
||||||
|
assert metas[: len(expected_metas)] == expected_metas
|
||||||
|
|
||||||
|
|
||||||
|
def test_library_artist(spa_html, no_api_auth, client, factories, settings):
|
||||||
|
album = factories["music.Album"]()
|
||||||
|
artist = album.artist
|
||||||
|
url = "/library/artists/{}".format(artist.pk)
|
||||||
|
|
||||||
|
response = client.get(url)
|
||||||
|
|
||||||
|
expected_metas = [
|
||||||
|
{
|
||||||
|
"tag": "meta",
|
||||||
|
"property": "og:url",
|
||||||
|
"content": utils.join_url(settings.FUNKWHALE_URL, url),
|
||||||
|
},
|
||||||
|
{"tag": "meta", "property": "og:title", "content": artist.name},
|
||||||
|
{"tag": "meta", "property": "og:type", "content": "profile"},
|
||||||
|
{
|
||||||
|
"tag": "meta",
|
||||||
|
"property": "og:image",
|
||||||
|
"content": utils.join_url(settings.FUNKWHALE_URL, album.cover.url),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
metas = utils.parse_meta(response.content.decode())
|
||||||
|
|
||||||
|
# we only test our custom metas, not the default ones
|
||||||
|
assert metas[: len(expected_metas)] == expected_metas
|
|
@ -6,8 +6,10 @@ import pytest
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from funkwhale_api.music import licenses, models, serializers, tasks, views
|
from funkwhale_api.common import utils
|
||||||
from funkwhale_api.federation import api_serializers as federation_api_serializers
|
from funkwhale_api.federation import api_serializers as federation_api_serializers
|
||||||
|
from funkwhale_api.federation import utils as federation_utils
|
||||||
|
from funkwhale_api.music import licenses, models, serializers, tasks, views
|
||||||
|
|
||||||
DATA_DIR = os.path.dirname(os.path.abspath(__file__))
|
DATA_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
@ -570,3 +572,74 @@ def test_detail_license(api_client, preferences):
|
||||||
response = api_client.get(url)
|
response = api_client.get(url)
|
||||||
|
|
||||||
assert response.data == expected
|
assert response.data == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_oembed_track(factories, no_api_auth, api_client, settings, preferences):
|
||||||
|
settings.FUNKWHALE_URL = "http://test"
|
||||||
|
settings.FUNKWHALE_EMBED_URL = "http://embed"
|
||||||
|
preferences["instance__name"] = "Hello"
|
||||||
|
track = factories["music.Track"]()
|
||||||
|
url = reverse("api:v1:oembed")
|
||||||
|
track_url = "https://test.com/library/tracks/{}".format(track.pk)
|
||||||
|
iframe_src = "http://embed?type=track&id={}".format(track.pk)
|
||||||
|
expected = {
|
||||||
|
"version": 1.0,
|
||||||
|
"type": "rich",
|
||||||
|
"provider_name": "{} - {}".format(
|
||||||
|
preferences["instance__name"], settings.APP_NAME
|
||||||
|
),
|
||||||
|
"provider_url": settings.FUNKWHALE_URL,
|
||||||
|
"height": 150,
|
||||||
|
"width": 600,
|
||||||
|
"title": "{} by {}".format(track.title, track.artist.name),
|
||||||
|
"description": track.full_name,
|
||||||
|
"thumbnail_url": federation_utils.full_url(
|
||||||
|
track.album.cover.crop["400x400"].url
|
||||||
|
),
|
||||||
|
"html": '<iframe width="600" height="150" scrolling="no" frameborder="no" src="{}"></iframe>'.format(
|
||||||
|
iframe_src
|
||||||
|
),
|
||||||
|
"author_name": track.artist.name,
|
||||||
|
"author_url": federation_utils.full_url(
|
||||||
|
utils.spa_reverse("library_artist", kwargs={"pk": track.artist.pk})
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
response = api_client.get(url, {"url": track_url, "format": "json"})
|
||||||
|
|
||||||
|
assert response.data == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_oembed_album(factories, no_api_auth, api_client, settings, preferences):
|
||||||
|
settings.FUNKWHALE_URL = "http://test"
|
||||||
|
settings.FUNKWHALE_EMBED_URL = "http://embed"
|
||||||
|
preferences["instance__name"] = "Hello"
|
||||||
|
track = factories["music.Track"]()
|
||||||
|
album = track.album
|
||||||
|
url = reverse("api:v1:oembed")
|
||||||
|
album_url = "https://test.com/library/albums/{}".format(album.pk)
|
||||||
|
iframe_src = "http://embed?type=album&id={}".format(album.pk)
|
||||||
|
expected = {
|
||||||
|
"version": 1.0,
|
||||||
|
"type": "rich",
|
||||||
|
"provider_name": "{} - {}".format(
|
||||||
|
preferences["instance__name"], settings.APP_NAME
|
||||||
|
),
|
||||||
|
"provider_url": settings.FUNKWHALE_URL,
|
||||||
|
"height": 400,
|
||||||
|
"width": 600,
|
||||||
|
"title": "{} by {}".format(album.title, album.artist.name),
|
||||||
|
"description": "{} by {}".format(album.title, album.artist.name),
|
||||||
|
"thumbnail_url": federation_utils.full_url(album.cover.crop["400x400"].url),
|
||||||
|
"html": '<iframe width="600" height="400" scrolling="no" frameborder="no" src="{}"></iframe>'.format(
|
||||||
|
iframe_src
|
||||||
|
),
|
||||||
|
"author_name": album.artist.name,
|
||||||
|
"author_url": federation_utils.full_url(
|
||||||
|
utils.spa_reverse("library_artist", kwargs={"pk": album.artist.pk})
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
response = api_client.get(url, {"url": album_url, "format": "json"})
|
||||||
|
|
||||||
|
assert response.data == expected
|
||||||
|
|
|
@ -24,17 +24,14 @@ server {
|
||||||
root /frontend;
|
root /frontend;
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
try_files $uri $uri/ @rewrites;
|
|
||||||
}
|
|
||||||
|
|
||||||
location @rewrites {
|
|
||||||
rewrite ^(.+)$ /index.html last;
|
|
||||||
}
|
|
||||||
location /api/ {
|
|
||||||
include /etc/nginx/funkwhale_proxy.conf;
|
include /etc/nginx/funkwhale_proxy.conf;
|
||||||
# this is needed if you have file import via upload enabled
|
# this is needed if you have file import via upload enabled
|
||||||
client_max_body_size ${NGINX_MAX_BODY_SIZE};
|
client_max_body_size ${NGINX_MAX_BODY_SIZE};
|
||||||
proxy_pass http://funkwhale-api/api/;
|
proxy_pass http://funkwhale-api/;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /front/ {
|
||||||
|
alias /frontend;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /federation/ {
|
location /federation/ {
|
||||||
|
|
|
@ -44,17 +44,14 @@ server {
|
||||||
root ${FUNKWHALE_FRONTEND_PATH};
|
root ${FUNKWHALE_FRONTEND_PATH};
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
try_files $uri $uri/ @rewrites;
|
|
||||||
}
|
|
||||||
|
|
||||||
location @rewrites {
|
|
||||||
rewrite ^(.+)$ /index.html last;
|
|
||||||
}
|
|
||||||
location /api/ {
|
|
||||||
include /etc/nginx/funkwhale_proxy.conf;
|
include /etc/nginx/funkwhale_proxy.conf;
|
||||||
# this is needed if you have file import via upload enabled
|
# this is needed if you have file import via upload enabled
|
||||||
client_max_body_size ${NGINX_MAX_BODY_SIZE};
|
client_max_body_size ${NGINX_MAX_BODY_SIZE};
|
||||||
proxy_pass http://funkwhale-api/api/;
|
proxy_pass http://funkwhale-api/;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /front/ {
|
||||||
|
alias ${FUNKWHALE_FRONTEND_PATH};
|
||||||
}
|
}
|
||||||
|
|
||||||
location /federation/ {
|
location /federation/ {
|
||||||
|
|
Loading…
Reference in New Issue