Skip to content
Snippets Groups Projects
Verified Commit 37b6dd40 authored by Eliot Berriot's avatar Eliot Berriot
Browse files

Merge branch 'release/0.6'

parents 4530e4f4 6011cf20
Branches
Tags 0.6
No related merge requests found
Showing
with 346 additions and 6 deletions
...@@ -5,6 +5,7 @@ from django.core.cache import cache as django_cache ...@@ -5,6 +5,7 @@ from django.core.cache import cache as django_cache
from dynamic_preferences.registries import global_preferences_registry from dynamic_preferences.registries import global_preferences_registry
from rest_framework.test import APIClient from rest_framework.test import APIClient
from funkwhale_api.activity import record
from funkwhale_api.taskapp import celery from funkwhale_api.taskapp import celery
...@@ -81,3 +82,28 @@ def superuser_client(db, factories, client): ...@@ -81,3 +82,28 @@ def superuser_client(db, factories, client):
setattr(client, 'user', user) setattr(client, 'user', user)
yield client yield client
delattr(client, 'user') delattr(client, 'user')
@pytest.fixture
def activity_registry():
r = record.registry
state = list(record.registry.items())
yield record.registry
record.registry.clear()
for key, value in state:
record.registry[key] = value
@pytest.fixture
def activity_registry():
r = record.registry
state = list(record.registry.items())
yield record.registry
record.registry.clear()
for key, value in state:
record.registry[key] = value
@pytest.fixture
def activity_muted(activity_registry, mocker):
yield mocker.patch.object(record, 'send')
from funkwhale_api.users.serializers import UserActivitySerializer
from funkwhale_api.music.serializers import TrackActivitySerializer
from funkwhale_api.favorites import serializers
from funkwhale_api.favorites import activities
def test_get_favorite_activity_url(settings, factories):
favorite = factories['favorites.TrackFavorite']()
user_url = favorite.user.get_activity_url()
expected = '{}/favorites/tracks/{}'.format(
user_url, favorite.pk)
assert favorite.get_activity_url() == expected
def test_activity_favorite_serializer(factories):
favorite = factories['favorites.TrackFavorite']()
actor = UserActivitySerializer(favorite.user).data
field = serializers.serializers.DateTimeField()
expected = {
"type": "Like",
"local_id": favorite.pk,
"id": favorite.get_activity_url(),
"actor": actor,
"object": TrackActivitySerializer(favorite.track).data,
"published": field.to_representation(favorite.creation_date),
}
data = serializers.TrackFavoriteActivitySerializer(favorite).data
assert data == expected
def test_track_favorite_serializer_is_connected(activity_registry):
conf = activity_registry['favorites.TrackFavorite']
assert conf['serializer'] == serializers.TrackFavoriteActivitySerializer
def test_track_favorite_serializer_instance_activity_consumer(
activity_registry):
conf = activity_registry['favorites.TrackFavorite']
consumer = activities.broadcast_track_favorite_to_instance_activity
assert consumer in conf['consumers']
def test_broadcast_track_favorite_to_instance_activity(
factories, mocker):
p = mocker.patch('funkwhale_api.common.channels.group_send')
favorite = factories['favorites.TrackFavorite']()
data = serializers.TrackFavoriteActivitySerializer(favorite).data
consumer = activities.broadcast_track_favorite_to_instance_activity
message = {
"type": 'event.send',
"text": '',
"data": data
}
consumer(data=data, obj=favorite)
p.assert_called_once_with('instance_activity', message)
def test_broadcast_track_favorite_to_instance_activity_private(
factories, mocker):
p = mocker.patch('funkwhale_api.common.channels.group_send')
favorite = factories['favorites.TrackFavorite'](
user__privacy_level='me'
)
data = serializers.TrackFavoriteActivitySerializer(favorite).data
consumer = activities.broadcast_track_favorite_to_instance_activity
message = {
"type": 'event.send',
"text": '',
"data": data
}
consumer(data=data, obj=favorite)
p.assert_not_called()
...@@ -33,7 +33,8 @@ def test_user_can_get_his_favorites(factories, logged_in_client, client): ...@@ -33,7 +33,8 @@ def test_user_can_get_his_favorites(factories, logged_in_client, client):
assert expected == parsed_json['results'] assert expected == parsed_json['results']
def test_user_can_add_favorite_via_api(factories, logged_in_client, client): def test_user_can_add_favorite_via_api(
factories, logged_in_client, activity_muted):
track = factories['music.Track']() track = factories['music.Track']()
url = reverse('api:v1:favorites:tracks-list') url = reverse('api:v1:favorites:tracks-list')
response = logged_in_client.post(url, {'track': track.pk}) response = logged_in_client.post(url, {'track': track.pk})
...@@ -51,6 +52,27 @@ def test_user_can_add_favorite_via_api(factories, logged_in_client, client): ...@@ -51,6 +52,27 @@ def test_user_can_add_favorite_via_api(factories, logged_in_client, client):
assert favorite.user == logged_in_client.user assert favorite.user == logged_in_client.user
def test_adding_favorites_calls_activity_record(
factories, logged_in_client, activity_muted):
track = factories['music.Track']()
url = reverse('api:v1:favorites:tracks-list')
response = logged_in_client.post(url, {'track': track.pk})
favorite = TrackFavorite.objects.latest('id')
expected = {
'track': track.pk,
'id': favorite.id,
'creation_date': favorite.creation_date.isoformat().replace('+00:00', 'Z'),
}
parsed_json = json.loads(response.content.decode('utf-8'))
assert expected == parsed_json
assert favorite.track == track
assert favorite.user == logged_in_client.user
activity_muted.assert_called_once_with(favorite)
def test_user_can_remove_favorite_via_api(logged_in_client, factories, client): def test_user_can_remove_favorite_via_api(logged_in_client, factories, client):
favorite = factories['favorites.TrackFavorite'](user=logged_in_client.user) favorite = factories['favorites.TrackFavorite'](user=logged_in_client.user)
url = reverse('api:v1:favorites:tracks-detail', kwargs={'pk': favorite.pk}) url = reverse('api:v1:favorites:tracks-detail', kwargs={'pk': favorite.pk})
......
from funkwhale_api.users.serializers import UserActivitySerializer
from funkwhale_api.music.serializers import TrackActivitySerializer
from funkwhale_api.history import serializers
from funkwhale_api.history import activities
def test_get_listening_activity_url(settings, factories):
listening = factories['history.Listening']()
user_url = listening.user.get_activity_url()
expected = '{}/listenings/tracks/{}'.format(
user_url, listening.pk)
assert listening.get_activity_url() == expected
def test_activity_listening_serializer(factories):
listening = factories['history.Listening']()
actor = UserActivitySerializer(listening.user).data
field = serializers.serializers.DateTimeField()
expected = {
"type": "Listen",
"local_id": listening.pk,
"id": listening.get_activity_url(),
"actor": actor,
"object": TrackActivitySerializer(listening.track).data,
"published": field.to_representation(listening.end_date),
}
data = serializers.ListeningActivitySerializer(listening).data
assert data == expected
def test_track_listening_serializer_is_connected(activity_registry):
conf = activity_registry['history.Listening']
assert conf['serializer'] == serializers.ListeningActivitySerializer
def test_track_listening_serializer_instance_activity_consumer(
activity_registry):
conf = activity_registry['history.Listening']
consumer = activities.broadcast_listening_to_instance_activity
assert consumer in conf['consumers']
def test_broadcast_listening_to_instance_activity(
factories, mocker):
p = mocker.patch('funkwhale_api.common.channels.group_send')
listening = factories['history.Listening']()
data = serializers.ListeningActivitySerializer(listening).data
consumer = activities.broadcast_listening_to_instance_activity
message = {
"type": 'event.send',
"text": '',
"data": data
}
consumer(data=data, obj=listening)
p.assert_called_once_with('instance_activity', message)
def test_broadcast_listening_to_instance_activity_private(
factories, mocker):
p = mocker.patch('funkwhale_api.common.channels.group_send')
listening = factories['history.Listening'](
user__privacy_level='me'
)
data = serializers.ListeningActivitySerializer(listening).data
consumer = activities.broadcast_listening_to_instance_activity
message = {
"type": 'event.send',
"text": '',
"data": data
}
consumer(data=data, obj=listening)
p.assert_not_called()
...@@ -28,7 +28,8 @@ def test_anonymous_user_can_create_listening_via_api(client, factories, settings ...@@ -28,7 +28,8 @@ def test_anonymous_user_can_create_listening_via_api(client, factories, settings
assert listening.session_key == client.session.session_key assert listening.session_key == client.session.session_key
def test_logged_in_user_can_create_listening_via_api(logged_in_client, factories): def test_logged_in_user_can_create_listening_via_api(
logged_in_client, factories, activity_muted):
track = factories['music.Track']() track = factories['music.Track']()
url = reverse('api:v1:history:listenings-list') url = reverse('api:v1:history:listenings-list')
...@@ -40,3 +41,17 @@ def test_logged_in_user_can_create_listening_via_api(logged_in_client, factories ...@@ -40,3 +41,17 @@ def test_logged_in_user_can_create_listening_via_api(logged_in_client, factories
assert listening.track == track assert listening.track == track
assert listening.user == logged_in_client.user assert listening.user == logged_in_client.user
def test_adding_listening_calls_activity_record(
factories, logged_in_client, activity_muted):
track = factories['music.Track']()
url = reverse('api:v1:history:listenings-list')
response = logged_in_client.post(url, {
'track': track.pk,
})
listening = models.Listening.objects.latest('id')
activity_muted.assert_called_once_with(listening)
from funkwhale_api.users.serializers import UserActivitySerializer
from funkwhale_api.favorites import serializers
def test_get_track_activity_url_mbid(factories):
track = factories['music.Track']()
expected = 'https://musicbrainz.org/recording/{}'.format(
track.mbid)
assert track.get_activity_url() == expected
def test_get_track_activity_url_no_mbid(settings, factories):
track = factories['music.Track'](mbid=None)
expected = settings.FUNKWHALE_URL + '/tracks/{}'.format(
track.pk)
assert track.get_activity_url() == expected
from funkwhale_api.users import serializers
def test_get_user_activity_url(settings, factories):
user = factories['users.User']()
assert user.get_activity_url() == '{}/@{}'.format(
settings.FUNKWHALE_URL, user.username)
def test_activity_user_serializer(factories):
user = factories['users.User']()
expected = {
"type": "Person",
"id": user.get_activity_url(),
"local_id": user.username,
"name": user.username,
}
data = serializers.UserActivitySerializer(user).data
assert data == expected
import json import json
import pytest
from django.test import RequestFactory from django.test import RequestFactory
from django.urls import reverse from django.urls import reverse
...@@ -116,3 +117,37 @@ def test_changing_password_updates_secret_key(logged_in_client): ...@@ -116,3 +117,37 @@ def test_changing_password_updates_secret_key(logged_in_client):
assert user.secret_key != secret_key assert user.secret_key != secret_key
assert user.password != password assert user.password != password
def test_user_can_patch_his_own_settings(logged_in_api_client):
user = logged_in_api_client.user
payload = {
'privacy_level': 'me',
}
url = reverse(
'api:v1:users:users-detail',
kwargs={'username': user.username})
response = logged_in_api_client.patch(url, payload)
assert response.status_code == 200
user.refresh_from_db()
assert user.privacy_level == 'me'
@pytest.mark.parametrize('method', ['put', 'patch'])
def test_user_cannot_patch_another_user(
method, logged_in_api_client, factories):
user = factories['users.User']()
payload = {
'privacy_level': 'me',
}
url = reverse(
'api:v1:users:users-detail',
kwargs={'username': user.username})
handler = getattr(logged_in_api_client, method)
response = handler(url, payload)
assert response.status_code == 403
...@@ -43,7 +43,6 @@ services: ...@@ -43,7 +43,6 @@ services:
restart: unless-stopped restart: unless-stopped
image: funkwhale/funkwhale:${FUNKWHALE_VERSION:-latest} image: funkwhale/funkwhale:${FUNKWHALE_VERSION:-latest}
env_file: .env env_file: .env
command: ./compose/django/gunicorn.sh
volumes: volumes:
- ./data/music:/music:ro - ./data/music:/music:ro
- ./data/media:/app/funkwhale_api/media - ./data/media:/app/funkwhale_api/media
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
# following variables: # following variables:
# - DJANGO_SECRET_KEY # - DJANGO_SECRET_KEY
# - DJANGO_ALLOWED_HOSTS # - DJANGO_ALLOWED_HOSTS
# - FUNKWHALE_URL
# Additionaly, on non-docker setup, you'll also have to tweak/uncomment those # Additionaly, on non-docker setup, you'll also have to tweak/uncomment those
# variables: # variables:
...@@ -28,6 +29,9 @@ FUNKWHALE_VERSION=latest ...@@ -28,6 +29,9 @@ FUNKWHALE_VERSION=latest
FUNKWHALE_API_IP=127.0.0.1 FUNKWHALE_API_IP=127.0.0.1
FUNKWHALE_API_PORT=5000 FUNKWHALE_API_PORT=5000
# Replace this by the definitive, public domain you will use for
# your instance
FUNKWHALE_URL=https.//yourdomain.funwhale
# API/Django configuration # API/Django configuration
......
...@@ -8,7 +8,7 @@ User=funkwhale ...@@ -8,7 +8,7 @@ User=funkwhale
# adapt this depending on the path of your funkwhale installation # adapt this depending on the path of your funkwhale installation
WorkingDirectory=/srv/funkwhale/api WorkingDirectory=/srv/funkwhale/api
EnvironmentFile=/srv/funkwhale/config/.env EnvironmentFile=/srv/funkwhale/config/.env
ExecStart=/srv/funkwhale/virtualenv/bin/gunicorn config.wsgi:application -b ${FUNKWHALE_API_IP}:${FUNKWHALE_API_PORT} ExecStart=/usr/local/bin/daphne -b ${FUNKWHALE_API_IP} -p ${FUNKWHALE_API_PORT} config.asgi:application
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
...@@ -19,6 +19,12 @@ server { ...@@ -19,6 +19,12 @@ server {
location / { return 301 https://$host$request_uri; } location / { return 301 https://$host$request_uri; }
} }
# required for websocket support
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server { server {
listen 443 ssl http2; listen 443 ssl http2;
listen [::]:443 ssl http2; listen [::]:443 ssl http2;
...@@ -51,6 +57,11 @@ server { ...@@ -51,6 +57,11 @@ server {
proxy_set_header X-Forwarded-Port $server_port; proxy_set_header X-Forwarded-Port $server_port;
proxy_redirect off; proxy_redirect off;
# websocket support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
location / { location / {
try_files $uri $uri/ @rewrites; try_files $uri $uri/ @rewrites;
} }
......
...@@ -36,6 +36,7 @@ services: ...@@ -36,6 +36,7 @@ services:
- C_FORCE_ROOT=true - C_FORCE_ROOT=true
- "DATABASE_URL=postgresql://postgres@postgres/postgres" - "DATABASE_URL=postgresql://postgres@postgres/postgres"
- "CACHE_URL=redis://redis:6379/0" - "CACHE_URL=redis://redis:6379/0"
- "FUNKWHALE_URL=http://funkwhale.test"
volumes: volumes:
- ./api:/app - ./api:/app
- ./data/music:/music - ./data/music:/music
...@@ -54,6 +55,7 @@ services: ...@@ -54,6 +55,7 @@ services:
- "DJANGO_SECRET_KEY=dev" - "DJANGO_SECRET_KEY=dev"
- "DATABASE_URL=postgresql://postgres@postgres/postgres" - "DATABASE_URL=postgresql://postgres@postgres/postgres"
- "CACHE_URL=redis://redis:6379/0" - "CACHE_URL=redis://redis:6379/0"
- "FUNKWHALE_URL=http://funkwhale.test"
links: links:
- postgres - postgres
- redis - redis
......
...@@ -28,6 +28,11 @@ http { ...@@ -28,6 +28,11 @@ http {
#gzip on; #gzip on;
proxy_cache_path /tmp/funkwhale-transcode levels=1:2 keys_zone=transcode:10m max_size=1g inactive=24h use_temp_path=off; proxy_cache_path /tmp/funkwhale-transcode levels=1:2 keys_zone=transcode:10m max_size=1g inactive=24h use_temp_path=off;
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server { server {
listen 6001; listen 6001;
charset utf-8; charset utf-8;
...@@ -40,6 +45,9 @@ http { ...@@ -40,6 +45,9 @@ http {
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host localhost:8080; proxy_set_header X-Forwarded-Host localhost:8080;
proxy_set_header X-Forwarded-Port 8080; proxy_set_header X-Forwarded-Port 8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_redirect off; proxy_redirect off;
location /_protected/media { location /_protected/media {
......
...@@ -3,8 +3,7 @@ FROM node:9 ...@@ -3,8 +3,7 @@ FROM node:9
EXPOSE 8080 EXPOSE 8080
WORKDIR /app/ WORKDIR /app/
ADD package.json . ADD package.json .
RUN yarn install --only=production RUN yarn install
RUN yarn install --only=dev
VOLUME ["/app/node_modules"] VOLUME ["/app/node_modules"]
COPY . . COPY . .
......
...@@ -32,6 +32,7 @@ module.exports = { ...@@ -32,6 +32,7 @@ module.exports = {
'/api': { '/api': {
target: 'http://nginx:6001', target: 'http://nginx:6001',
changeOrigin: true, changeOrigin: true,
ws: true
}, },
'/media': { '/media': {
target: 'http://nginx:6001', target: 'http://nginx:6001',
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
"dependencies": { "dependencies": {
"axios": "^0.17.1", "axios": "^0.17.1",
"dateformat": "^2.0.0", "dateformat": "^2.0.0",
"django-channels": "^1.1.6",
"js-logger": "^1.3.0", "js-logger": "^1.3.0",
"jwt-decode": "^2.2.0", "jwt-decode": "^2.2.0",
"lodash": "^4.17.4", "lodash": "^4.17.4",
......
...@@ -33,6 +33,9 @@ ...@@ -33,6 +33,9 @@
</template> </template>
<script> <script>
import { WebSocketBridge } from 'django-channels'
import logger from '@/logging'
import Sidebar from '@/components/Sidebar' import Sidebar from '@/components/Sidebar'
import Raven from '@/components/Raven' import Raven from '@/components/Raven'
...@@ -44,6 +47,31 @@ export default { ...@@ -44,6 +47,31 @@ export default {
}, },
created () { created () {
this.$store.dispatch('instance/fetchSettings') this.$store.dispatch('instance/fetchSettings')
this.openWebsocket()
let self = this
setInterval(() => {
// used to redraw ago dates every minute
self.$store.commit('ui/computeLastDate')
}, 1000 * 60)
},
methods: {
openWebsocket () {
let self = this
let token = this.$store.state.auth.token
// let token = 'test'
const bridge = new WebSocketBridge()
bridge.connect(
`/api/v1/instance/activity?token=${token}`,
null,
{reconnectInterval: 5000})
bridge.listen(function (event) {
logger.default.info('Received timeline update', event)
self.$store.commit('instance/event', event)
})
bridge.socket.addEventListener('open', function () {
console.log('Connected to WebSocket')
})
}
} }
} }
</script> </script>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment