Verified Commit 37b6dd40 authored by Eliot Berriot's avatar Eliot Berriot
Browse files

Merge branch 'release/0.6'

parents 4530e4f4 6011cf20
......@@ -5,6 +5,7 @@ from django.core.cache import cache as django_cache
from dynamic_preferences.registries import global_preferences_registry
from rest_framework.test import APIClient
from funkwhale_api.activity import record
from funkwhale_api.taskapp import celery
......@@ -81,3 +82,28 @@ def superuser_client(db, factories, client):
setattr(client, 'user', user)
yield client
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):
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']()
url = reverse('api:v1:favorites:tracks-list')
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):
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):
favorite = factories['favorites.TrackFavorite'](user=logged_in_client.user)
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
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']()
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
assert listening.track == track
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 pytest
from django.test import RequestFactory
from django.urls import reverse
......@@ -116,3 +117,37 @@ def test_changing_password_updates_secret_key(logged_in_client):
assert user.secret_key != secret_key
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:
restart: unless-stopped
image: funkwhale/funkwhale:${FUNKWHALE_VERSION:-latest}
env_file: .env
command: ./compose/django/gunicorn.sh
volumes:
- ./data/music:/music:ro
- ./data/media:/app/funkwhale_api/media
......
......@@ -2,6 +2,7 @@
# following variables:
# - DJANGO_SECRET_KEY
# - DJANGO_ALLOWED_HOSTS
# - FUNKWHALE_URL
# Additionaly, on non-docker setup, you'll also have to tweak/uncomment those
# variables:
......@@ -28,6 +29,9 @@ FUNKWHALE_VERSION=latest
FUNKWHALE_API_IP=127.0.0.1
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
......
......@@ -8,7 +8,7 @@ User=funkwhale
# adapt this depending on the path of your funkwhale installation
WorkingDirectory=/srv/funkwhale/api
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]
WantedBy=multi-user.target
......@@ -19,6 +19,12 @@ server {
location / { return 301 https://$host$request_uri; }
}
# required for websocket support
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
......@@ -51,6 +57,11 @@ server {
proxy_set_header X-Forwarded-Port $server_port;
proxy_redirect off;
# websocket support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
location / {
try_files $uri $uri/ @rewrites;
}
......
......@@ -36,6 +36,7 @@ services:
- C_FORCE_ROOT=true
- "DATABASE_URL=postgresql://postgres@postgres/postgres"
- "CACHE_URL=redis://redis:6379/0"
- "FUNKWHALE_URL=http://funkwhale.test"
volumes:
- ./api:/app
- ./data/music:/music
......@@ -54,6 +55,7 @@ services:
- "DJANGO_SECRET_KEY=dev"
- "DATABASE_URL=postgresql://postgres@postgres/postgres"
- "CACHE_URL=redis://redis:6379/0"
- "FUNKWHALE_URL=http://funkwhale.test"
links:
- postgres
- redis
......
......@@ -28,6 +28,11 @@ http {
#gzip on;
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 {
listen 6001;
charset utf-8;
......@@ -40,6 +45,9 @@ http {
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host localhost: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;
location /_protected/media {
......
......@@ -3,8 +3,7 @@ FROM node:9
EXPOSE 8080
WORKDIR /app/
ADD package.json .
RUN yarn install --only=production
RUN yarn install --only=dev
RUN yarn install
VOLUME ["/app/node_modules"]
COPY . .
......
......@@ -32,6 +32,7 @@ module.exports = {
'/api': {
target: 'http://nginx:6001',
changeOrigin: true,
ws: true
},
'/media': {
target: 'http://nginx:6001',
......
......@@ -17,6 +17,7 @@
"dependencies": {
"axios": "^0.17.1",
"dateformat": "^2.0.0",
"django-channels": "^1.1.6",
"js-logger": "^1.3.0",
"jwt-decode": "^2.2.0",
"lodash": "^4.17.4",
......
......@@ -33,6 +33,9 @@
</template>
<script>
import { WebSocketBridge } from 'django-channels'
import logger from '@/logging'
import Sidebar from '@/components/Sidebar'
import Raven from '@/components/Raven'
......@@ -44,6 +47,31 @@ export default {
},
created () {
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>
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment