Verified Commit bb3ed760 authored by Agate's avatar Agate 💬

Merge branch 'release/0.4'

parents c7d6ad95 a38ca1ed
BACKEND_URL=http://localhost:6001
API_AUTHENTICATION_REQUIRED=True
CACHALOT_ENABLED=False
RAVEN_ENABLED=false
RAVEN_DSN=https://44332e9fdd3d42879c7d35bf8562c6a4:0062dc16a22b41679cd5765e5342f716@sentry.eliotberriot.com/5
......@@ -2,7 +2,7 @@ variables:
IMAGE_NAME: funkwhale/funkwhale
IMAGE: $IMAGE_NAME:$CI_COMMIT_REF_NAME
IMAGE_LATEST: $IMAGE_NAME:latest
PIP_CACHE_DIR: "$CI_PROJECT_DIR/pip-cache"
stages:
......@@ -14,40 +14,62 @@ test_api:
services:
- postgres:9.4
stage: test
image: funkwhale/funkwhale:base
image: funkwhale/funkwhale:latest
cache:
key: "$CI_PROJECT_ID/pip_cache"
paths:
- "$PIP_CACHE_DIR"
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/pip-cache"
DJANGO_ALLOWED_HOSTS: "localhost"
DATABASE_URL: "postgresql://postgres@postgres/postgres"
before_script:
- python3 -m venv --copies virtualenv
- source virtualenv/bin/activate
- cd api
- pip install -r requirements/base.txt
- pip install -r requirements/local.txt
- pip install -r requirements/test.txt
script:
- pytest
tags:
- docker
test_front:
stage: test
image: node:9
before_script:
- cd front
script:
- yarn install
- yarn run unit
cache:
key: "$CI_JOB_NAME-$CI_COMMIT_REF_NAME"
key: "$CI_PROJECT_ID/front_dependencies"
paths:
- "$CI_PROJECT_DIR/pip-cache"
- front/node_modules
- front/yarn.lock
artifacts:
name: "front_${CI_COMMIT_REF_NAME}"
paths:
- front/dist/
tags:
- docker
build_front:
stage: build
image: node:6-alpine
image: node:9
before_script:
- cd front
script:
- npm install
- npm run build
- yarn install
- yarn run build
cache:
key: "$CI_COMMIT_REF_NAME"
key: "$CI_PROJECT_ID/front_dependencies"
paths:
- front/node_modules
- front/yarn.lock
artifacts:
name: "front_${CI_COMMIT_REF_NAME}"
paths:
......
......@@ -2,8 +2,23 @@ Changelog
=========
0.3.5 (Unreleased)
------------------
0.5 (Unreleased)
----------------
0.4 (2018-02-18)
----------------
- Front: ambiant colors in player based on current track cover (#59)
- Front: simplified front dev setup thanks to webpack proxy (#59)
- Front: added some unittests for the store (#55)
- Front: fixed broken login redirection when 401
- Front: Removed autoplay on page reload
- API: Added a /instance/settings endpoint
- Front: load /instance/settings on page load
- Added settings to report JS and Python error to a Sentry instance
This is disabled by default, but feel free to enable it if you want
to help us by sending your error reports :) (#8)
0.3.5 (2018-01-07)
......
from rest_framework import routers
from django.conf.urls import include, url
from funkwhale_api.instance import views as instance_views
from funkwhale_api.music import views
from funkwhale_api.playlists import views as playlists_views
from rest_framework_jwt import views as jwt_views
......@@ -25,6 +26,10 @@ router.register(
v1_patterns = router.urls
v1_patterns += [
url(r'^instance/',
include(
('funkwhale_api.instance.urls', 'instance'),
namespace='instance')),
url(r'^providers/',
include(
('funkwhale_api.providers.urls', 'providers'),
......
......@@ -12,6 +12,7 @@ from __future__ import absolute_import, unicode_literals
import os
import environ
from funkwhale_api import __version__
ROOT_DIR = environ.Path(__file__) - 3 # (/a/b/myfile.py - 3 = /)
APPS_DIR = ROOT_DIR.path('funkwhale_api')
......@@ -22,6 +23,10 @@ try:
env.read_env(ROOT_DIR.file('.env'))
except FileNotFoundError:
pass
ALLOWED_HOSTS = env.list('DJANGO_ALLOWED_HOSTS')
# APP CONFIGURATION
# ------------------------------------------------------------------------------
DJANGO_APPS = (
......@@ -56,10 +61,28 @@ THIRD_PARTY_APPS = (
'django_filters',
)
# Sentry
RAVEN_ENABLED = env.bool("RAVEN_ENABLED", default=False)
RAVEN_DSN = env("RAVEN_DSN", default='')
if RAVEN_ENABLED:
RAVEN_CONFIG = {
'dsn': RAVEN_DSN,
# If you are using git, you can also automatically configure the
# release based on the git info.
'release': __version__,
}
THIRD_PARTY_APPS += (
'raven.contrib.django.raven_compat',
)
# Apps specific for this project go here.
LOCAL_APPS = (
'funkwhale_api.users', # custom users app
# Your stuff: custom apps go here
'funkwhale_api.instance',
'funkwhale_api.music',
'funkwhale_api.favorites',
'funkwhale_api.radios',
......@@ -71,6 +94,7 @@ LOCAL_APPS = (
)
# See: https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
# MIDDLEWARE CONFIGURATION
......
......@@ -54,7 +54,6 @@ SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
# ------------------------------------------------------------------------------
# Hosts/domain names that are valid for this site
# See https://docs.djangoproject.com/en/1.6/ref/settings/#allowed-hosts
ALLOWED_HOSTS = env.list('DJANGO_ALLOWED_HOSTS')
CSRF_TRUSTED_ORIGINS = ALLOWED_HOSTS
# END SITE CONFIGURATION
......
# -*- coding: utf-8 -*-
__version__ = '0.3.5'
__version__ = '0.4'
__version_info__ = tuple([int(num) if num.isdigit() else num for num in __version__.replace('-', '.', 1).split('.')])
......@@ -43,7 +43,7 @@ class TrackFavoriteViewSet(mixins.CreateModelMixin,
favorite = models.TrackFavorite.add(track=track, user=self.request.user)
return favorite
@list_route(methods=['delete'])
@list_route(methods=['delete', 'post'])
def remove(self, request, *args, **kwargs):
try:
pk = int(request.data['track'])
......
from dynamic_preferences import types
from dynamic_preferences.registries import global_preferences_registry
raven = types.Section('raven')
@global_preferences_registry.register
class RavenDSN(types.StringPreference):
show_in_api = True
section = raven
name = 'front_dsn'
default = 'https://9e0562d46b09442bb8f6844e50cbca2b@sentry.eliotberriot.com/4'
verbose_name = (
'A raven DSN key used to report front-ent errors to '
'a sentry instance'
)
help_text = (
'Keeping the default one will report errors to funkwhale developers'
)
SENTRY_HELP_TEXT = (
'Error reporting is disabled by default but you can enable it if'
' you want to help us improve funkwhale'
)
@global_preferences_registry.register
class RavenEnabled(types.BooleanPreference):
show_in_api = True
section = raven
name = 'front_enabled'
default = False
verbose_name = (
'Wether error reporting to a Sentry instance using raven is enabled'
' for front-end errors'
)
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^settings/$', views.InstanceSettings.as_view(), name='settings'),
]
from rest_framework import views
from rest_framework.response import Response
from dynamic_preferences.api import serializers
from dynamic_preferences.registries import global_preferences_registry
class InstanceSettings(views.APIView):
permission_classes = []
authentication_classes = []
def get(self, request, *args, **kwargs):
manager = global_preferences_registry.manager()
manager.all()
all_preferences = manager.model.objects.all().order_by(
'section', 'name'
)
api_preferences = [
p
for p in all_preferences
if getattr(p.preference, 'show_in_api', False)
]
data = serializers.GlobalPreferenceSerializer(
api_preferences, many=True).data
return Response(data, status=200)
......@@ -4,7 +4,7 @@ Populates the database with fake data
import random
from funkwhale_api.music import models
from funkwhale_api.music.tests import factories
from funkwhale_api.music import factories
def create_data(count=25):
......@@ -19,4 +19,4 @@ def create_data(count=25):
if __name__ == '__main__':
main()
create_data()
......@@ -31,10 +31,7 @@ class TrackFileSerializer(serializers.ModelSerializer):
fields = ('id', 'path', 'duration', 'source', 'filename', 'track')
def get_path(self, o):
request = self.context.get('request')
url = o.path
if request:
url = request.build_absolute_uri(url)
return url
......
......@@ -47,8 +47,7 @@ mutagen>=1.39,<1.40
# Until this is merged
#django-taggit>=0.22,<0.23
git+https://github.com/alex/django-taggit.git@95776ac66948ed7ba7c12e35c1170551e3be66a5
django-taggit>=0.22,<0.23
# Until this is merged
git+https://github.com/EliotBerriot/PyMemoize.git@django
# Until this is merged
......@@ -57,3 +56,4 @@ git+https://github.com/EliotBerriot/django-cachalot.git@django-2
django-dynamic-preferences>=1.5,<1.6
pyacoustid>=1.1.5,<1.2
raven>=6.5,<7
......@@ -10,6 +10,7 @@ services:
volumes:
- .:/app
environment:
- "DJANGO_ALLOWED_HOSTS=localhost"
- "DATABASE_URL=postgresql://postgres@postgres/postgres"
postgres:
image: postgres
......@@ -3,6 +3,7 @@ import shutil
import pytest
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.taskapp import celery
......@@ -29,7 +30,9 @@ def factories(db):
@pytest.fixture
def preferences(db):
yield global_preferences_registry.manager()
manager = global_preferences_registry.manager()
manager.all()
yield manager
@pytest.fixture
......@@ -48,6 +51,11 @@ def logged_in_client(db, factories, client):
delattr(client, 'user')
@pytest.fixture
def api_client(client):
return APIClient()
@pytest.fixture
def superuser_client(db, factories, client):
user = factories['users.SuperUser']()
......
from django.urls import reverse
from dynamic_preferences.api import serializers
def test_can_list_settings_via_api(preferences, api_client):
url = reverse('api:v1:instance:settings')
all_preferences = preferences.model.objects.all()
expected_preferences = {
p.preference.identifier(): p
for p in all_preferences
if getattr(p.preference, 'show_in_api', False)}
assert len(expected_preferences) > 0
response = api_client.get(url)
assert response.status_code == 200
assert len(response.data) == len(expected_preferences)
for p in response.data:
i = '__'.join([p['section'], p['name']])
assert i in expected_preferences
......@@ -58,11 +58,14 @@ def test_user_can_remove_favorite_via_api(logged_in_client, factories, client):
assert response.status_code == 204
assert TrackFavorite.objects.count() == 0
def test_user_can_remove_favorite_via_api_using_track_id(factories, logged_in_client):
@pytest.mark.parametrize('method', ['delete', 'post'])
def test_user_can_remove_favorite_via_api_using_track_id(
method, factories, logged_in_client):
favorite = factories['favorites.TrackFavorite'](user=logged_in_client.user)
url = reverse('api:v1:favorites:tracks-remove')
response = logged_in_client.delete(
response = getattr(logged_in_client, method)(
url, json.dumps({'track': favorite.track.pk}),
content_type='application/json'
)
......
......@@ -78,3 +78,10 @@ API_AUTHENTICATION_REQUIRED=True
# public: anybody can register an account
# disabled: nobody can register an account
REGISTRATION_MODE=disabled
# Sentry/Raven error reporting (server side)
# Enable Raven if you want to help improve funkwhale by
# automatically sending error reports our Sentry instance.
# This will help us detect and correct bugs
RAVEN_ENABLED=false
RAVEN_DSN=https://44332e9fdd3d42879c7d35bf8562c6a4:0062dc16a22b41679cd5765e5342f716@sentry.eliotberriot.com/5
......@@ -3,9 +3,7 @@ version: '2'
services:
front:
build:
dockerfile: docker/Dockerfile.dev
context: ./front
build: front
env_file: .env.dev
environment:
- "HOST=0.0.0.0"
......@@ -51,13 +49,11 @@ services:
- ./api:/app
- ./data/music:/music
environment:
- "DJANGO_ALLOWED_HOSTS=localhost"
- "DJANGO_ALLOWED_HOSTS=localhost,nginx"
- "DJANGO_SETTINGS_MODULE=config.settings.local"
- "DJANGO_SECRET_KEY=dev"
- "DATABASE_URL=postgresql://postgres@postgres/postgres"
- "CACHE_URL=redis://redis:6379/0"
ports:
- "12081:12081"
links:
- postgres
- redis
......
......@@ -40,8 +40,8 @@ http {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host:$server_port;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-Host localhost:8080;
proxy_set_header X-Forwarded-Port 8080;
proxy_redirect off;
proxy_pass http://api:12081/;
}
......
FROM node:6-alpine
FROM node:9
EXPOSE 8080
RUN mkdir /app
WORKDIR /app
WORKDIR /app/
ADD package.json .
RUN yarn install --only=production
RUN yarn install --only=dev
VOLUME ["/app/node_modules"]
COPY . .
RUN npm install
ADD . .
RUN npm run build
CMD ["npm", "run", "dev"]
......@@ -28,7 +28,20 @@ module.exports = {
autoOpenBrowser: true,
assetsSubDirectory: 'static',
assetsPublicPath: '/',
proxyTable: {},
proxyTable: {
'/api': {
target: 'http://nginx:6001',
changeOrigin: true,
},
'/media': {
target: 'http://nginx:6001',
changeOrigin: true,
},
'/staticfiles': {
target: 'http://nginx:6001',
changeOrigin: true,
}
},
// CSS Sourcemaps off by default because relative paths are "buggy"
// with this option, according to the CSS-Loader README
// (https://github.com/webpack/css-loader#sourcemaps)
......
module.exports = {
NODE_ENV: '"production"',
BACKEND_URL: '"' + (process.env.BACKEND_URL || '/') + '"'
BACKEND_URL: '"/"'
}
FROM node:6-alpine
EXPOSE 8080
RUN mkdir /app
WORKDIR /app
ADD package.json .
RUN npm install
VOLUME ["/app/node_modules"]
CMD ["npm", "run", "dev"]
......@@ -9,24 +9,28 @@
"start": "node build/dev-server.js",
"build": "node build/build.js",
"unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run",
"unit-watch": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js",
"e2e": "node test/e2e/runner.js",
"test": "npm run unit && npm run e2e",
"lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs"
},
"dependencies": {
"axios": "^0.17.1",
"dateformat": "^2.0.0",
"js-logger": "^1.3.0",
"jwt-decode": "^2.2.0",
"lodash": "^4.17.4",
"moxios": "^0.4.0",
"raven-js": "^3.22.3",
"semantic-ui-css": "^2.2.10",
"vue": "^2.3.3",
"vue-lazyload": "^1.1.4",
"vue-resource": "^1.3.4",
"vue-router": "^2.3.1",
"vue-upload-component": "^2.7.4",
"vuedraggable": "^2.14.1",
"vuex": "^3.0.1",
"vuex-persistedstate": "^2.4.2"
"vuex-persistedstate": "^2.4.2",
"vuex-router-sync": "^5.0.0"
},
"devDependencies": {
"autoprefixer": "^6.7.2",
......@@ -46,6 +50,7 @@
"cross-env": "^4.0.0",
"cross-spawn": "^5.0.1",
"css-loader": "^0.28.0",
"es6-promise": "^4.2.2",
"eslint": "^3.19.0",
"eslint-config-standard": "^6.2.1",
"eslint-friendly-formatter": "^2.0.7",
......@@ -67,6 +72,7 @@
"karma-phantomjs-launcher": "^1.0.2",
"karma-phantomjs-shim": "^1.4.0",
"karma-sinon-chai": "^1.3.1",
"karma-sinon-stub-promise": "^1.0.0",
"karma-sourcemap-loader": "^0.3.7",
"karma-spec-reporter": "0.0.30",
"karma-webpack": "^2.0.2",
......@@ -85,6 +91,7 @@
"shelljs": "^0.7.6",
"sinon": "^2.1.0",
"sinon-chai": "^2.8.0",
"sinon-stub-promise": "^4.0.0",
"url-loader": "^0.5.8",
"vue-loader": "^12.1.0",
"vue-style-loader": "^3.0.1",
......
......@@ -22,15 +22,26 @@
</div>
</div>
</div>
<raven
v-if="$store.state.instance.settings.raven.front_enabled.value"
:dsn="$store.state.instance.settings.raven.front_dsn.value">
</raven>
</div>
</template>
<script>
import Sidebar from '@/components/Sidebar'
import Raven from '@/components/Raven'
export default {
name: 'app',
components: { Sidebar }
components: {
Sidebar,
Raven
},
created () {
this.$store.dispatch('instance/fetchSettings')
}
}
</script>
......@@ -40,25 +51,33 @@ export default {
// and we end up with CSS rules not applied,
// see https://github.com/webpack/webpack/issues/215
@import 'semantic/semantic.css';
@import 'style/vendor/media';
html, body {
@include media("<desktop") {
font-size: 200%;
}
}
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.main.pusher, .footer {
margin-left: 350px !important;
@include media(">desktop") {
margin-left: 350px !important;
}
transform: none !important;
}
.main-pusher {
padding: 1.5rem 0;
}
#footer {
padding: 4em;
}
.ui.stripe.segment {