From ab6484e6ec004b493dc2464692e25cc372a19fd1 Mon Sep 17 00:00:00 2001 From: Virgile Robles Date: Tue, 13 Apr 2021 22:32:15 +0200 Subject: [PATCH 01/19] Correctly set default value in systemd unit --- deploy/funkwhale-worker.service | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deploy/funkwhale-worker.service b/deploy/funkwhale-worker.service index fbc242081..934263bd6 100644 --- a/deploy/funkwhale-worker.service +++ b/deploy/funkwhale-worker.service @@ -7,8 +7,9 @@ PartOf=funkwhale.target User=funkwhale # adapt this depending on the path of your funkwhale installation WorkingDirectory=/srv/funkwhale/api +Environment="CELERYD_CONCURRENCY=0" EnvironmentFile=/srv/funkwhale/config/.env -ExecStart=/srv/funkwhale/virtualenv/bin/celery -A funkwhale_api.taskapp worker -l INFO --concurrency=${CELERYD_CONCURRENCY-0} +ExecStart=/srv/funkwhale/virtualenv/bin/celery -A funkwhale_api.taskapp worker -l INFO --concurrency=${CELERYD_CONCURRENCY} [Install] From 6c34b143b36769be007c609825775f76ffccd8b5 Mon Sep 17 00:00:00 2001 From: Virgile Robles Date: Tue, 13 Apr 2021 22:35:08 +0200 Subject: [PATCH 02/19] Add changelog fragment --- changes/changelog.d/1160.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/changelog.d/1160.bugfix diff --git a/changes/changelog.d/1160.bugfix b/changes/changelog.d/1160.bugfix new file mode 100644 index 000000000..ba01f1114 --- /dev/null +++ b/changes/changelog.d/1160.bugfix @@ -0,0 +1 @@ +Fixed systemd unit for funkwhale-worker (#1160) From c43cd2f8bf6d26a65dc85a2b35776334caf3a3c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ciar=C3=A1n=20Ainsworth?= Date: Fri, 30 Apr 2021 21:11:44 +0000 Subject: [PATCH 03/19] Resolve "Channel: clicking auf "Subscribe" when not logged in still updates the subscriber count" --- changes/changelog.d/1296.enhancement | 1 + front/scripts/fix-fomantic-css.py | 1 + .../components/channels/SubscribeButton.vue | 26 +++++++- front/src/components/common/LoginModal.vue | 65 +++++++++++++++++++ 4 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 changes/changelog.d/1296.enhancement create mode 100644 front/src/components/common/LoginModal.vue diff --git a/changes/changelog.d/1296.enhancement b/changes/changelog.d/1296.enhancement new file mode 100644 index 000000000..2df4117aa --- /dev/null +++ b/changes/changelog.d/1296.enhancement @@ -0,0 +1 @@ +Added modal to prompt users to log in when subscribing to channels (#1296) diff --git a/front/scripts/fix-fomantic-css.py b/front/scripts/fix-fomantic-css.py index 075d818dd..3ab13001f 100755 --- a/front/scripts/fix-fomantic-css.py +++ b/front/scripts/fix-fomantic-css.py @@ -164,6 +164,7 @@ def discard_unused_icons(rule): ".wikipedia", ".wrench", ".x", + ".key", ] if ":before" not in rule["lines"][0]: return False diff --git a/front/src/components/channels/SubscribeButton.vue b/front/src/components/channels/SubscribeButton.vue index 6a087554b..3fb06be29 100644 --- a/front/src/components/channels/SubscribeButton.vue +++ b/front/src/components/channels/SubscribeButton.vue @@ -1,16 +1,33 @@ From e67faee3efdd72bee0f707e1d22995e0f3a3c05a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ciar=C3=A1n=20Ainsworth?= Date: Sat, 1 May 2021 10:38:49 +0000 Subject: [PATCH 04/19] Resolve "Fix loaders in dark theme" --- changes/changelog.d/1442.bugfix | 1 + front/src/style/_main.scss | 1 + front/src/style/components/_loaders.scss | 7 +++++++ 3 files changed, 9 insertions(+) create mode 100644 changes/changelog.d/1442.bugfix create mode 100644 front/src/style/components/_loaders.scss diff --git a/changes/changelog.d/1442.bugfix b/changes/changelog.d/1442.bugfix new file mode 100644 index 000000000..685d3781d --- /dev/null +++ b/changes/changelog.d/1442.bugfix @@ -0,0 +1 @@ +Fixed minor graphical bug where loaders would appear white in dark theme (#1442) diff --git a/front/src/style/_main.scss b/front/src/style/_main.scss index 5e41ffc14..678205fca 100644 --- a/front/src/style/_main.scss +++ b/front/src/style/_main.scss @@ -47,6 +47,7 @@ $bottom-player-height: 4rem; @import "./components/_track_table.scss"; @import "./components/_user_link.scss"; @import "./components/_volume_control.scss"; +@import "./components/_loaders.scss"; @import "./pages/_about.scss"; @import "./pages/_admin_account_detail.scss"; diff --git a/front/src/style/components/_loaders.scss b/front/src/style/components/_loaders.scss new file mode 100644 index 000000000..d8ede0fb7 --- /dev/null +++ b/front/src/style/components/_loaders.scss @@ -0,0 +1,7 @@ +.ui.dimmer { + background-color: var(--dimmer-background); +} + +.ui.loading.form:before { + background-color: var(--dimmer-background); +} From 0176b29daef990b4a3caea16b48ad8d7e449b677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ciar=C3=A1n=20Ainsworth?= Date: Sun, 9 May 2021 05:38:55 +0000 Subject: [PATCH 05/19] Add missing playable serializer --- api/funkwhale_api/music/serializers.py | 5 ++++- api/tests/music/test_serializers.py | 1 + changes/changelog.d/playable.bugfix | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 changes/changelog.d/playable.bugfix diff --git a/api/funkwhale_api/music/serializers.py b/api/funkwhale_api/music/serializers.py index cc78358cb..6e2c0fe2c 100644 --- a/api/funkwhale_api/music/serializers.py +++ b/api/funkwhale_api/music/serializers.py @@ -201,7 +201,6 @@ class AlbumSerializer(OptionalDescriptionMixin, serializers.Serializer): release_date = serializers.DateField() creation_date = serializers.DateTimeField() is_local = serializers.BooleanField() - is_playable = serializers.SerializerMethodField() get_attributed_to = serialize_attributed_to @@ -302,6 +301,7 @@ class TrackSerializer(OptionalDescriptionMixin, serializers.Serializer): license = serializers.SerializerMethodField() cover = cover_field get_attributed_to = serialize_attributed_to + is_playable = serializers.SerializerMethodField() def get_artist(self, o): return serialize_artist_simple(o.artist) @@ -323,6 +323,9 @@ class TrackSerializer(OptionalDescriptionMixin, serializers.Serializer): def get_license(self, o): return o.license_id + def get_is_playable(self, obj): + return bool(getattr(obj, "playable_uploads", [])) + @common_serializers.track_fields_for_update("name", "description", "privacy_level") class LibraryForOwnerSerializer(serializers.ModelSerializer): diff --git a/api/tests/music/test_serializers.py b/api/tests/music/test_serializers.py index c0bb2298f..a0746d3b8 100644 --- a/api/tests/music/test_serializers.py +++ b/api/tests/music/test_serializers.py @@ -255,6 +255,7 @@ def test_track_serializer(factories, to_api_date): "attributed_to": federation_serializers.APIActorSerializer(actor).data, "cover": common_serializers.AttachmentSerializer(track.attachment_cover).data, "downloads_count": track.downloads_count, + "is_playable": bool(track.playable_uploads), } serializer = serializers.TrackSerializer(track) assert serializer.data == expected diff --git a/changes/changelog.d/playable.bugfix b/changes/changelog.d/playable.bugfix new file mode 100644 index 000000000..331ab8421 --- /dev/null +++ b/changes/changelog.d/playable.bugfix @@ -0,0 +1 @@ +Added missing is_playable serializer for the tracks endpoint. From f38fca69622f86cf8ec47ff7915183dd52e49b53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ciar=C3=A1n=20Ainsworth?= Date: Mon, 3 May 2021 08:14:13 +0000 Subject: [PATCH 06/19] Fix minor Vue and sanitize errors (cherry picked from commit 26f8f7db9f9a80207d260d532181d34e61d1b235) --- front/package.json | 6 +- front/src/components/auth/ApplicationForm.vue | 2 +- front/src/components/auth/Plugin.vue | 1 - .../src/components/federation/FetchButton.vue | 3 +- front/src/sanitize.js | 2 +- front/vue.config.js | 7 +- front/yarn.lock | 121 +++++++++++++----- 7 files changed, 97 insertions(+), 45 deletions(-) diff --git a/front/package.json b/front/package.json index dc918569a..1553b206b 100644 --- a/front/package.json +++ b/front/package.json @@ -15,7 +15,7 @@ "postinstall": "yarn run fix-fomantic-css" }, "dependencies": { - "axios": "^0.18.0", + "axios": "^0.21.1", "axios-auth-refresh": "^2.2.6", "core-js": "^3.6.4", "diff": "^4.0.1", @@ -24,11 +24,11 @@ "fomantic-ui-css": "^2.8.3", "howler": "^2.2.1", "js-logger": "^1.4.1", - "lodash": "^4.17.10", + "lodash": "^4.17.21", "moment": "^2.22.2", "qs": "^6.7.0", "register-service-worker": "^1.6.2", - "sanitize-html": "^1.20.1", + "sanitize-html": "^2.3.3", "sass": "^1.26.5", "showdown": "^1.8.6", "text-clipper": "^1.3.0", diff --git a/front/src/components/auth/ApplicationForm.vue b/front/src/components/auth/ApplicationForm.vue index 34449bd1a..4ddd5bc1a 100644 --- a/front/src/components/auth/ApplicationForm.vue +++ b/front/src/components/auth/ApplicationForm.vue @@ -64,7 +64,7 @@ diff --git a/front/src/components/auth/Plugin.vue b/front/src/components/auth/Plugin.vue index 7c679062c..571cd7f55 100644 --- a/front/src/components/auth/Plugin.vue +++ b/front/src/components/auth/Plugin.vue @@ -48,7 +48,6 @@
-
diff --git a/front/src/components/federation/FetchButton.vue b/front/src/components/federation/FetchButton.vue index c1266095e..c19756fb7 100644 --- a/front/src/components/federation/FetchButton.vue +++ b/front/src/components/federation/FetchButton.vue @@ -84,7 +84,8 @@
+ +
diff --git a/front/src/sanitize.js b/front/src/sanitize.js index 4e42d5a4b..d2a8d0809 100644 --- a/front/src/sanitize.js +++ b/front/src/sanitize.js @@ -39,5 +39,5 @@ const allowedAttributes = { } export default function sanitize(input) { - return sanitizeHtml(input, {allowedAttributes, allowedAttributes}) + return sanitizeHtml(input, {allowedAttributes, allowedTags}) } diff --git a/front/vue.config.js b/front/vue.config.js index d01bb0087..aa38127af 100644 --- a/front/vue.config.js +++ b/front/vue.config.js @@ -2,10 +2,10 @@ const baseUrl = process.env.BASE_URL || '/front/' const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; const webpack = require('webpack'); -const PurgecssPlugin = require('purgecss-webpack-plugin') +//const PurgecssPlugin = require('purgecss-webpack-plugin') const PreloadWebpackPlugin = require('preload-webpack-plugin'); -const glob = require('glob-all') -const path = require('path') +//const glob = require('glob-all') +//const path = require('path') let plugins = [ // do not include moment.js locales since it's quite heavy new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), @@ -32,6 +32,7 @@ if (process.env.BUNDLE_ANALYZE === '1') { // whitelistPatternsChildren: [/plyr/, /dropdown/, /upward/] // }), // ) + module.exports = { publicPath: baseUrl, productionSourceMap: false, diff --git a/front/yarn.lock b/front/yarn.lock index 0bf143cb3..baf56f595 100644 --- a/front/yarn.lock +++ b/front/yarn.lock @@ -1933,13 +1933,12 @@ axios-auth-refresh@^2.2.6: resolved "https://registry.yarnpkg.com/axios-auth-refresh/-/axios-auth-refresh-2.2.8.tgz#de420b6b5d6efdb4ad3666e44c38960a9b08f382" integrity sha512-WR59uCgO9VppC9VQU6vtszrAHnF3RtylkGltOGldfB4Rw+my0j9WdJuvRzMwiwTh+LmG/SQWzgeCfFYf8N4FIA== -axios@^0.18.0: - version "0.18.1" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.18.1.tgz#ff3f0de2e7b5d180e757ad98000f1081b87bcea3" - integrity sha512-0BfJq4NSfQXd+SkFdrvFbG7addhYSBA2mQwISr46pD6E5iqkWg02RAs8vyTT/j0RTnoYmeXauBuSv1qKwR179g== +axios@^0.21.1: + version "0.21.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8" + integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA== dependencies: - follow-redirects "1.5.10" - is-buffer "^2.0.2" + follow-redirects "^1.10.0" babel-eslint@^10.0.3: version "10.1.0" @@ -2753,6 +2752,11 @@ colorette@^1.2.1: resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b" integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw== +colorette@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" + integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== + combined-stream@^1.0.6, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -3281,7 +3285,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3: dependencies: ms "2.0.0" -debug@3.1.0, debug@=3.1.0: +debug@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== @@ -3562,6 +3566,11 @@ domelementtype@^2.0.1: resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.0.1.tgz#1f8bdfe91f5a78063274e803b4bdcedf6e94f94d" integrity sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ== +domelementtype@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57" + integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A== + domexception@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90" @@ -3583,6 +3592,13 @@ domhandler@^3.0.0: dependencies: domelementtype "^2.0.1" +domhandler@^4.0.0, domhandler@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.2.0.tgz#f9768a5f034be60a89a27c2e4d0f74eba0d8b059" + integrity sha512-zk7sgt970kzPks2Bf+dwT/PLzghLnsivb9CcxkvR8Mzr66Olr0Ofd8neSbglHJHaHa2MadfoSdNlKYAaafmWfA== + dependencies: + domelementtype "^2.2.0" + domutils@1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" @@ -3599,14 +3615,14 @@ domutils@^1.5.1, domutils@^1.7.0: dom-serializer "0" domelementtype "1" -domutils@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.2.0.tgz#f3ce1610af5c30280bde1b71f84b018b958f32cf" - integrity sha512-0haAxVr1PR0SqYwCH7mxMpHZUwjih9oPPedqpR/KufsnxPyZ9dyVw1R5093qnJF3WXSbjBkdzRWLw/knJV/fAg== +domutils@^2.5.2: + version "2.6.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.6.0.tgz#2e15c04185d43fb16ae7057cb76433c6edb938b7" + integrity sha512-y0BezHuy4MDYxh6OvolXYsH+1EMGmFbwv5FKW7ovwMG6zTPWqNPq3WF9ayZssFq+UlKdffGLbOEaghNdaOm1WA== dependencies: dom-serializer "^1.0.1" - domelementtype "^2.0.1" - domhandler "^3.0.0" + domelementtype "^2.2.0" + domhandler "^4.2.0" dot-prop@^5.2.0: version "5.2.0" @@ -3822,6 +3838,11 @@ escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1 resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + escodegen@^1.11.1: version "1.14.3" resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" @@ -4389,18 +4410,16 @@ focus-trap@^5.1.0: tabbable "^4.0.0" xtend "^4.0.1" -follow-redirects@1.5.10: - version "1.5.10" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" - integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ== - dependencies: - debug "=3.1.0" - follow-redirects@^1.0.0: version "1.13.0" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db" integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA== +follow-redirects@^1.10.0: + version "1.14.0" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.0.tgz#f5d260f95c5f8c105894491feee5dc8993b402fe" + integrity sha512-0vRwd7RKQBTt+mgu87mtYeofLFZpTas2S9zY+jIeuLJMNvudIgF52nr19q40HOwH5RrhWIPuj9puybzSJiRrVg== + fomantic-ui-css@^2.8.3: version "2.8.6" resolved "https://registry.yarnpkg.com/fomantic-ui-css/-/fomantic-ui-css-2.8.6.tgz#65bb7d4534e12557bfa82128c32578db3d05186d" @@ -4945,14 +4964,14 @@ htmlparser2@^3.3.0, htmlparser2@^3.8.2, htmlparser2@^3.9.1: inherits "^2.0.1" readable-stream "^3.1.1" -htmlparser2@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-4.1.0.tgz#9a4ef161f2e4625ebf7dfbe6c0a2f52d18a59e78" - integrity sha512-4zDq1a1zhE4gQso/c5LP1OtrhYTncXNSpvJYtWJBtXAETPlMfi3IFNjGuQbYLuVY4ZR0QMqRVvo4Pdy9KLyP8Q== +htmlparser2@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7" + integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A== dependencies: domelementtype "^2.0.1" - domhandler "^3.0.0" - domutils "^2.0.0" + domhandler "^4.0.0" + domutils "^2.5.2" entities "^2.0.0" http-deceiver@^1.2.7: @@ -5277,7 +5296,7 @@ is-buffer@^1.1.5: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== -is-buffer@^2.0.2, is-buffer@~2.0.3: +is-buffer@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.4.tgz#3e572f23c8411a5cfd9557c849e3665e0b290623" integrity sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A== @@ -5455,6 +5474,11 @@ is-plain-object@^2.0.3, is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" +is-plain-object@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" + integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== + is-promise@^2.0.0: version "2.2.2" resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" @@ -5804,6 +5828,11 @@ kind-of@^6.0.0, kind-of@^6.0.2: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== +klona@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.4.tgz#7bb1e3affb0cb8624547ef7e8f6708ea2e39dfc0" + integrity sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA== + launch-editor-middleware@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/launch-editor-middleware/-/launch-editor-middleware-2.2.1.tgz#e14b07e6c7154b0a4b86a0fd345784e45804c157" @@ -5961,11 +5990,16 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.3, lodash@^4.17.4: +lodash@^4.15.0, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.3, lodash@^4.17.4: version "4.17.20" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + log-symbols@2.2.0, log-symbols@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" @@ -6436,6 +6470,11 @@ nan@^2.12.1: resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01" integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw== +nanoid@^3.1.22: + version "3.1.22" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.22.tgz#b35f8fb7d151990a8aebd5aa5015c03cf726f844" + integrity sha512-/2ZUaJX2ANuLtTvqTlgqBQNJoQO398KyJgZloL0PZkC0dpysjncRUPsFe3DUPzz/y3h+u7C46np8RMuvF3jsSQ== + nanomatch@^1.2.9: version "1.2.13" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" @@ -7506,6 +7545,15 @@ postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.27, postcss@^7.0.3 source-map "^0.6.1" supports-color "^6.1.0" +postcss@^8.0.2: + version "8.2.13" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.13.tgz#dbe043e26e3c068e45113b1ed6375d2d37e2129f" + integrity sha512-FCE5xLH+hjbzRdpbRb1IMCvPv9yZx2QnDarBEYSN0N0HYk+TcXsEhwdFcFb+SRWOKzKGErhIEbBK2ogyLdTtfQ== + dependencies: + colorette "^1.2.2" + nanoid "^3.1.22" + source-map "^0.6.1" + preload-webpack-plugin@^3.0.0-beta.4: version "3.0.0-beta.4" resolved "https://registry.yarnpkg.com/preload-webpack-plugin/-/preload-webpack-plugin-3.0.0-beta.4.tgz#b8a36046df3b4a1b61db55d92f1a5aebdb99d246" @@ -8218,15 +8266,18 @@ safe-regex@^1.1.0: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sanitize-html@^1.20.1: - version "1.27.3" - resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-1.27.3.tgz#dc7419b075f96737055c98764aea40c42d978df9" - integrity sha512-79tcPlgJ3fuK0/TtUCIBdPeQSvktTSTJP9O/dzrteaO98qw5UV6CATh3ZyPjUzv1LtNjHDlhbq9XOXiKf0zA1w== +sanitize-html@^2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.3.3.tgz#3db382c9a621cce4c46d90f10c64f1e9da9e8353" + integrity sha512-DCFXPt7Di0c6JUnlT90eIgrjs6TsJl/8HYU3KLdmrVclFN4O0heTcVbJiMa23OKVr6aR051XYtsgd8EWwEBwUA== dependencies: - htmlparser2 "^4.1.0" - lodash "^4.17.15" + deepmerge "^4.2.2" + escape-string-regexp "^4.0.0" + htmlparser2 "^6.0.0" + is-plain-object "^5.0.0" + klona "^2.0.3" parse-srcset "^1.0.2" - postcss "^7.0.27" + postcss "^8.0.2" sass-loader@^8.0.2: version "8.0.2" From c60bc93930514bde133e8dfb9edf8a5d29194f1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ciar=C3=A1n=20Ainsworth?= Date: Wed, 19 May 2021 12:59:14 +0000 Subject: [PATCH 07/19] Fix recently listened widget and simple artist serializer (cherry picked from commit 071ff89a4f52d31090a4a23603a66356e1e47c0c) --- api/funkwhale_api/history/views.py | 4 +++- front/src/components/audio/track/Widget.vue | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/api/funkwhale_api/history/views.py b/api/funkwhale_api/history/views.py index a14917fc1..0a7992756 100644 --- a/api/funkwhale_api/history/views.py +++ b/api/funkwhale_api/history/views.py @@ -56,7 +56,9 @@ class ListeningViewSet( ) tracks = Track.objects.with_playable_uploads( music_utils.get_actor_from_request(self.request) - ).select_related("artist", "album__artist", "attributed_to") + ).select_related( + "artist", "album__artist", "attributed_to", "artist__attachment_cover" + ) return queryset.prefetch_related(Prefetch("track", queryset=tracks)) def get_serializer_context(self): diff --git a/front/src/components/audio/track/Widget.vue b/front/src/components/audio/track/Widget.vue index 4092d054d..68d6988a9 100644 --- a/front/src/components/audio/track/Widget.vue +++ b/front/src/components/audio/track/Widget.vue @@ -8,6 +8,8 @@
+ +
From ea4be336d485cacadfe8b4b5b74c6aa56bd8120b Mon Sep 17 00:00:00 2001 From: Georg Krause Date: Wed, 19 May 2021 15:30:51 +0200 Subject: [PATCH 08/19] Version bump and changelog for 1.1.2 --- CHANGELOG | 19 +++++++++++++++++++ api/funkwhale_api/__init__.py | 2 +- changes/changelog.d/1160.bugfix | 1 - changes/changelog.d/1296.enhancement | 1 - changes/changelog.d/1442.bugfix | 1 - changes/changelog.d/playable.bugfix | 1 - 6 files changed, 20 insertions(+), 5 deletions(-) delete mode 100644 changes/changelog.d/1160.bugfix delete mode 100644 changes/changelog.d/1296.enhancement delete mode 100644 changes/changelog.d/1442.bugfix delete mode 100644 changes/changelog.d/playable.bugfix diff --git a/CHANGELOG b/CHANGELOG index fac975a89..f1a7c0283 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,25 @@ This changelog is viewable on the web at https://docs.funkwhale.audio/changelog. .. towncrier +1.1.2 (2021-05-19) +------------------ + +Upgrade instructions are available at +https://docs.funkwhale.audio/admin/upgrading.html + +Enhancements: + +- Added modal to prompt users to log in when subscribing to channels (#1296) + + +Bugfixes: + +- Added missing is_playable serializer for the tracks endpoint. +- Fixed minor graphical bug where loaders would appear white in dark theme (#1442) +- Fixed systemd unit for funkwhale-worker (#1160) +- Several minor fixes for the Frontend + + 1.1.1 (2021-04-13) ------------------ diff --git a/api/funkwhale_api/__init__.py b/api/funkwhale_api/__init__.py index 8afc6a1d2..9efdef3c1 100644 --- a/api/funkwhale_api/__init__.py +++ b/api/funkwhale_api/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -__version__ = "1.1.1" +__version__ = "1.1.2" __version_info__ = tuple( [ int(num) if num.isdigit() else num diff --git a/changes/changelog.d/1160.bugfix b/changes/changelog.d/1160.bugfix deleted file mode 100644 index ba01f1114..000000000 --- a/changes/changelog.d/1160.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fixed systemd unit for funkwhale-worker (#1160) diff --git a/changes/changelog.d/1296.enhancement b/changes/changelog.d/1296.enhancement deleted file mode 100644 index 2df4117aa..000000000 --- a/changes/changelog.d/1296.enhancement +++ /dev/null @@ -1 +0,0 @@ -Added modal to prompt users to log in when subscribing to channels (#1296) diff --git a/changes/changelog.d/1442.bugfix b/changes/changelog.d/1442.bugfix deleted file mode 100644 index 685d3781d..000000000 --- a/changes/changelog.d/1442.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fixed minor graphical bug where loaders would appear white in dark theme (#1442) diff --git a/changes/changelog.d/playable.bugfix b/changes/changelog.d/playable.bugfix deleted file mode 100644 index 331ab8421..000000000 --- a/changes/changelog.d/playable.bugfix +++ /dev/null @@ -1 +0,0 @@ -Added missing is_playable serializer for the tracks endpoint. From 6e1be964d72e3cbac598a89932b137347c2cdc02 Mon Sep 17 00:00:00 2001 From: Georg Krause Date: Thu, 17 Jun 2021 11:21:40 +0000 Subject: [PATCH 09/19] Merge branch '1498-fix-scrobber-empty-mbid' into 'develop' Fix the scrobbler plugin submitting literal "None" as MusicBrainz ID Closes #1498 See merge request funkwhale/funkwhale!1326 (cherry picked from commit 8273feb581742be0b513df170071fa059e55a094) 572efc79 Fix the scrobbler plugin submitting literal "None" as MusicBrainz ID --- api/funkwhale_api/contrib/scrobbler/scrobbler.py | 4 ++-- changes/changelog.d/1498.bug | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 changes/changelog.d/1498.bug diff --git a/api/funkwhale_api/contrib/scrobbler/scrobbler.py b/api/funkwhale_api/contrib/scrobbler/scrobbler.py index f5a7ddd3f..2d18ee778 100644 --- a/api/funkwhale_api/contrib/scrobbler/scrobbler.py +++ b/api/funkwhale_api/contrib/scrobbler/scrobbler.py @@ -90,7 +90,7 @@ def get_scrobble_payload(track, date, suffix="[0]"): "l{}".format(suffix): upload.duration if upload else 0, "b{}".format(suffix): (track.album.title if track.album else "") or "", "n{}".format(suffix): track.position or "", - "m{}".format(suffix): str(track.mbid) or "", + "m{}".format(suffix): str(track.mbid or ""), "o{}".format(suffix): "P", # Source: P = chosen by user } if date: @@ -115,7 +115,7 @@ def get_scrobble2_payload(track, date, suffix="[0]"): if track.position: data["trackNumber"] = track.position if track.mbid: - data["mbid"] = str(track.mbid) + data["mbid"] = str(track.mbid or "") if date: offset = upload.duration / 2 if upload.duration else 0 data["timestamp"] = int(int(date.timestamp()) - offset) diff --git a/changes/changelog.d/1498.bug b/changes/changelog.d/1498.bug new file mode 100644 index 000000000..5290c9e2a --- /dev/null +++ b/changes/changelog.d/1498.bug @@ -0,0 +1 @@ +Fix the scrobbler plugin submitting literal "None" as MusicBrainz ID (#1498) From d44fc7e6f354a701aaaf41d1406571bf3a607391 Mon Sep 17 00:00:00 2001 From: Georg Krause Date: Fri, 2 Jul 2021 20:56:37 +0000 Subject: [PATCH 10/19] Add Worker-Src to CSP-Header of nginx config, #1489 (cherry picked from commit 1f3239f01dc0cd924d23a8c49d61f2361321b4bb) --- changes/changelog.d/1489.bugfix | 1 + deploy/nginx.template | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 changes/changelog.d/1489.bugfix diff --git a/changes/changelog.d/1489.bugfix b/changes/changelog.d/1489.bugfix new file mode 100644 index 000000000..dd73b4205 --- /dev/null +++ b/changes/changelog.d/1489.bugfix @@ -0,0 +1 @@ +Add worker-src to nginx header to prevent issues (#1489) diff --git a/deploy/nginx.template b/deploy/nginx.template index aaa3cec7d..575030bba 100644 --- a/deploy/nginx.template +++ b/deploy/nginx.template @@ -44,7 +44,7 @@ server { # If you are using S3 to host your files, remember to add your S3 URL to the # media-src and img-src headers (e.g. img-src 'self' https:// data:) - add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; media-src 'self' data:"; + add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; media-src 'self' data:; worker-src 'self'"; add_header Referrer-Policy "strict-origin-when-cross-origin"; root ${FUNKWHALE_FRONTEND_PATH}; @@ -84,7 +84,7 @@ server { } location /front/ { - add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; media-src 'self' data:"; + add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; media-src 'self' data:; worker-src 'self'"; add_header Referrer-Policy "strict-origin-when-cross-origin"; add_header Service-Worker-Allowed "/"; add_header X-Frame-Options "SAMEORIGIN"; @@ -94,7 +94,7 @@ server { add_header Cache-Control "public, must-revalidate, proxy-revalidate"; } location /front/embed.html { - add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; media-src 'self' data:"; + add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; media-src 'self' data:; worker-src 'self'"; add_header Referrer-Policy "strict-origin-when-cross-origin"; add_header X-Frame-Options "ALLOW"; From 73f4fa14541a14f58c5b4ad96674c8835d6b7589 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ciar=C3=A1n=20Ainsworth?= Date: Mon, 26 Jul 2021 18:08:14 +0000 Subject: [PATCH 11/19] Add beforeRouteEnter guards for moderation pages --- front/.eslintrc.js | 24 ++ front/src/router/index.js | 638 ++++++++++++++++++++------------------ front/src/store/auth.js | 84 +++-- 3 files changed, 400 insertions(+), 346 deletions(-) create mode 100644 front/.eslintrc.js diff --git a/front/.eslintrc.js b/front/.eslintrc.js new file mode 100644 index 000000000..64de441fc --- /dev/null +++ b/front/.eslintrc.js @@ -0,0 +1,24 @@ +module.exports = { + env: { + browser: true, + es6: true + }, + extends: [ + 'plugin:vue/recommended', + 'standard' + ], + globals: { + Atomics: 'readonly', + SharedArrayBuffer: 'readonly' + }, + parserOptions: { + ecmaVersion: 2018, + sourceType: 'module', + parser: 'babel-eslint' + }, + plugins: [ + 'vue' + ], + rules: { + } +} diff --git a/front/src/router/index.js b/front/src/router/index.js index d3ce3f4d4..314bf5b1d 100644 --- a/front/src/router/index.js +++ b/front/src/router/index.js @@ -1,107 +1,135 @@ -import Vue from "vue" -import Router from "vue-router" +import Vue from 'vue' +import Router from 'vue-router' +import store from '@/store' Vue.use(Router) +function adminPermissions (to, from, next) { + if (store.state.auth.authenticated === true && store.state.auth.availablePermissions.settings === true) { + next() + } else { + console.log('Not authenticated. Redirecting to library.') + next({ name: 'library.index' }) + } +} + +function moderatorPermissions (to, from, next) { + if (store.state.auth.authenticated === true && store.state.auth.availablePermissions.moderation === true) { + next() + } else { + console.log('Not authenticated. Redirecting to library.') + next({ name: 'library.index' }) + } +} + +function libraryPermissions (to, from, next) { + if (store.state.auth.authenticated === true && store.state.auth.availablePermissions.library === true) { + next() + } else { + console.log('Not authenticated. Redirecting to library.') + next({ name: 'library.index' }) + } +} + console.log('PROCESS', process.env) export default new Router({ - mode: "history", - linkActiveClass: "active", - base: process.env.VUE_APP_ROUTER_BASE_URL || "/", - scrollBehavior(to, from, savedPosition) { + mode: 'history', + linkActiveClass: 'active', + base: process.env.VUE_APP_ROUTER_BASE_URL || '/', + scrollBehavior (to, from, savedPosition) { if (to.meta.preserveScrollPosition) { return savedPosition } return new Promise(resolve => { setTimeout(() => { if (to.hash) { - resolve({ selector: to.hash }); + resolve({ selector: to.hash }) } - let pos = savedPosition || { x: 0, y: 0 }; - resolve(pos); - }, 100); - }); + const pos = savedPosition || { x: 0, y: 0 } + resolve(pos) + }, 100) + }) }, routes: [ { - path: "/", - name: "index", + path: '/', + name: 'index', component: () => - import(/* webpackChunkName: "core" */ "@/components/Home") + import(/* webpackChunkName: "core" */ '@/components/Home') }, { - path: "/front", - name: "front", + path: '/front', + name: 'front', redirect: to => { const { hash, query } = to return { name: 'index', hash, query } } }, { - path: "/about", - name: "about", + path: '/about', + name: 'about', component: () => - import(/* webpackChunkName: "about" */ "@/components/About") + import(/* webpackChunkName: "about" */ '@/components/About') }, { - path: "/login", - name: "login", + path: '/login', + name: 'login', component: () => - import(/* webpackChunkName: "login" */ "@/views/auth/Login"), - props: route => ({ next: route.query.next || "/library" }) + import(/* webpackChunkName: "login" */ '@/views/auth/Login'), + props: route => ({ next: route.query.next || '/library' }) }, { - path: "/notifications", - name: "notifications", + path: '/notifications', + name: 'notifications', component: () => - import(/* webpackChunkName: "notifications" */ "@/views/Notifications") + import(/* webpackChunkName: "notifications" */ '@/views/Notifications') }, { - path: "/auth/password/reset", - name: "auth.password-reset", + path: '/auth/password/reset', + name: 'auth.password-reset', component: () => - import(/* webpackChunkName: "password-reset" */ "@/views/auth/PasswordReset"), + import(/* webpackChunkName: "password-reset" */ '@/views/auth/PasswordReset'), props: route => ({ defaultEmail: route.query.email }) }, { - path: "/auth/callback", - name: "auth.callback", + path: '/auth/callback', + name: 'auth.callback', component: () => - import(/* webpackChunkName: "auth-callback" */ "@/views/auth/Callback"), + import(/* webpackChunkName: "auth-callback" */ '@/views/auth/Callback'), props: route => ({ code: route.query.code, - state: route.query.state, + state: route.query.state }) }, { - path: "/auth/email/confirm", - name: "auth.email-confirm", + path: '/auth/email/confirm', + name: 'auth.email-confirm', component: () => - import(/* webpackChunkName: "signup" */ "@/views/auth/EmailConfirm"), + import(/* webpackChunkName: "signup" */ '@/views/auth/EmailConfirm'), props: route => ({ defaultKey: route.query.key }) }, { - path: "/search", - name: "search", + path: '/search', + name: 'search', component: () => - import(/* webpackChunkName: "core" */ "@/views/Search"), + import(/* webpackChunkName: "core" */ '@/views/Search'), props: route => ({ initialId: route.query.id, initialType: route.query.type || 'artists', initialQuery: route.query.q, - initialPage: parseInt(route.query.page) || 1, + initialPage: parseInt(route.query.page) || 1 }) }, { - path: "/auth/password/reset/confirm", - name: "auth.password-reset-confirm", + path: '/auth/password/reset/confirm', + name: 'auth.password-reset-confirm', component: () => import( - /* webpackChunkName: "password-reset" */ "@/views/auth/PasswordResetConfirm" + /* webpackChunkName: "password-reset" */ '@/views/auth/PasswordResetConfirm' ), props: route => ({ defaultUid: route.query.uid, @@ -109,10 +137,10 @@ export default new Router({ }) }, { - path: "/authorize", - name: "authorize", + path: '/authorize', + name: 'authorize', component: () => - import(/* webpackChunkName: "settings" */ "@/components/auth/Authorize"), + import(/* webpackChunkName: "settings" */ '@/components/auth/Authorize'), props: route => ({ clientId: route.query.client_id, redirectUri: route.query.redirect_uri, @@ -123,29 +151,29 @@ export default new Router({ }) }, { - path: "/signup", - name: "signup", + path: '/signup', + name: 'signup', component: () => - import(/* webpackChunkName: "signup" */ "@/views/auth/Signup"), + import(/* webpackChunkName: "signup" */ '@/views/auth/Signup'), props: route => ({ defaultInvitation: route.query.invitation }) }, { - path: "/logout", - name: "logout", + path: '/logout', + name: 'logout', component: () => - import(/* webpackChunkName: "login" */ "@/components/auth/Logout") + import(/* webpackChunkName: "login" */ '@/components/auth/Logout') }, { - path: "/settings", - name: "settings", + path: '/settings', + name: 'settings', component: () => - import(/* webpackChunkName: "settings" */ "@/components/auth/Settings") + import(/* webpackChunkName: "settings" */ '@/components/auth/Settings') }, { - path: "/settings/applications/new", - name: "settings.applications.new", + path: '/settings/applications/new', + name: 'settings.applications.new', props: route => ({ scopes: route.query.scopes, name: route.query.name, @@ -153,58 +181,58 @@ export default new Router({ }), component: () => import( - /* webpackChunkName: "settings" */ "@/components/auth/ApplicationNew" + /* webpackChunkName: "settings" */ '@/components/auth/ApplicationNew' ) }, { - path: "/settings/plugins", - name: "settings.plugins", + path: '/settings/plugins', + name: 'settings.plugins', component: () => import( - /* webpackChunkName: "settings" */ "@/views/auth/Plugins" + /* webpackChunkName: "settings" */ '@/views/auth/Plugins' ) }, { - path: "/settings/applications/:id/edit", - name: "settings.applications.edit", + path: '/settings/applications/:id/edit', + name: 'settings.applications.edit', component: () => import( - /* webpackChunkName: "settings" */ "@/components/auth/ApplicationEdit" + /* webpackChunkName: "settings" */ '@/components/auth/ApplicationEdit' ), props: true }, - ...[{suffix: '.full', path: '/@:username@:domain'}, {suffix: '', path: '/@:username'}].map((route) => { + ...[{ suffix: '.full', path: '/@:username@:domain' }, { suffix: '', path: '/@:username' }].map((route) => { return { path: route.path, name: `profile${route.suffix}`, component: () => - import(/* webpackChunkName: "core" */ "@/views/auth/ProfileBase"), + import(/* webpackChunkName: "core" */ '@/views/auth/ProfileBase'), props: true, children: [ { - path: "", + path: '', name: `profile${route.suffix}.overview`, component: () => import( - /* webpackChunkName: "core" */ "@/views/auth/ProfileOverview" + /* webpackChunkName: "core" */ '@/views/auth/ProfileOverview' ) }, { - path: "activity", + path: 'activity', name: `profile${route.suffix}.activity`, component: () => import( - /* webpackChunkName: "core" */ "@/views/auth/ProfileActivity" + /* webpackChunkName: "core" */ '@/views/auth/ProfileActivity' ) - }, + } ] } }), { - path: "/favorites", - name: "favorites", + path: '/favorites', + name: 'favorites', component: () => - import(/* webpackChunkName: "favorites" */ "@/components/favorites/List"), + import(/* webpackChunkName: "favorites" */ '@/components/favorites/List'), props: route => ({ defaultOrdering: route.query.ordering, defaultPage: route.query.page, @@ -212,29 +240,29 @@ export default new Router({ }) }, { - path: "/content", + path: '/content', component: () => - import(/* webpackChunkName: "core" */ "@/views/content/Base"), + import(/* webpackChunkName: "core" */ '@/views/content/Base'), children: [ { - path: "", - name: "content.index", + path: '', + name: 'content.index', component: () => - import(/* webpackChunkName: "core" */ "@/views/content/Home") + import(/* webpackChunkName: "core" */ '@/views/content/Home') } ] }, { - path: "/content/libraries/tracks", + path: '/content/libraries/tracks', component: () => - import(/* webpackChunkName: "auth-libraries" */ "@/views/content/Base"), + import(/* webpackChunkName: "auth-libraries" */ '@/views/content/Base'), children: [ { - path: "", - name: "content.libraries.files", + path: '', + name: 'content.libraries.files', component: () => import( - /* webpackChunkName: "auth-libraries" */ "@/views/content/libraries/Files" + /* webpackChunkName: "auth-libraries" */ '@/views/content/libraries/Files' ), props: route => ({ query: route.query.q @@ -243,50 +271,52 @@ export default new Router({ ] }, { - path: "/content/libraries", + path: '/content/libraries', component: () => - import(/* webpackChunkName: "auth-libraries" */ "@/views/content/Base"), + import(/* webpackChunkName: "auth-libraries" */ '@/views/content/Base'), children: [ { - path: "", - name: "content.libraries.index", + path: '', + name: 'content.libraries.index', component: () => import( - /* webpackChunkName: "auth-libraries" */ "@/views/content/libraries/Home" + /* webpackChunkName: "auth-libraries" */ '@/views/content/libraries/Home' ) } ] }, { - path: "/content/remote", + path: '/content/remote', component: () => - import(/* webpackChunkName: "auth-libraries" */ "@/views/content/Base"), + import(/* webpackChunkName: "auth-libraries" */ '@/views/content/Base'), children: [ { - path: "", - name: "content.remote.index", + path: '', + name: 'content.remote.index', component: () => - import(/* webpackChunkName: "auth-libraries" */ "@/views/content/remote/Home") + import(/* webpackChunkName: "auth-libraries" */ '@/views/content/remote/Home') } ] }, { - path: "/manage/settings", - name: "manage.settings", + path: '/manage/settings', + name: 'manage.settings', + beforeEnter: adminPermissions, component: () => - import(/* webpackChunkName: "admin" */ "@/views/admin/Settings") + import(/* webpackChunkName: "admin" */ '@/views/admin/Settings') }, { - path: "/manage/library", + path: '/manage/library', + beforeEnter: libraryPermissions, component: () => - import(/* webpackChunkName: "admin" */ "@/views/admin/library/Base"), + import(/* webpackChunkName: "admin" */ '@/views/admin/library/Base'), children: [ { - path: "edits", - name: "manage.library.edits", + path: 'edits', + name: 'manage.library.edits', component: () => import( - /* webpackChunkName: "admin" */ "@/views/admin/library/EditsList" + /* webpackChunkName: "admin" */ '@/views/admin/library/EditsList' ), props: route => { return { @@ -295,11 +325,11 @@ export default new Router({ } }, { - path: "artists", - name: "manage.library.artists", + path: 'artists', + name: 'manage.library.artists', component: () => import( - /* webpackChunkName: "admin" */ "@/views/admin/library/ArtistsList" + /* webpackChunkName: "admin" */ '@/views/admin/library/ArtistsList' ), props: route => { return { @@ -308,20 +338,20 @@ export default new Router({ } }, { - path: "artists/:id", - name: "manage.library.artists.detail", + path: 'artists/:id', + name: 'manage.library.artists.detail', component: () => import( - /* webpackChunkName: "admin" */ "@/views/admin/library/ArtistDetail" + /* webpackChunkName: "admin" */ '@/views/admin/library/ArtistDetail' ), props: true }, { - path: "channels", - name: "manage.channels", + path: 'channels', + name: 'manage.channels', component: () => import( - /* webpackChunkName: "admin" */ "@/views/admin/ChannelsList" + /* webpackChunkName: "admin" */ '@/views/admin/ChannelsList' ), props: route => { return { @@ -330,20 +360,20 @@ export default new Router({ } }, { - path: "channels/:id", - name: "manage.channels.detail", + path: 'channels/:id', + name: 'manage.channels.detail', component: () => import( - /* webpackChunkName: "admin" */ "@/views/admin/ChannelDetail" + /* webpackChunkName: "admin" */ '@/views/admin/ChannelDetail' ), props: true }, { - path: "albums", - name: "manage.library.albums", + path: 'albums', + name: 'manage.library.albums', component: () => import( - /* webpackChunkName: "admin" */ "@/views/admin/library/AlbumsList" + /* webpackChunkName: "admin" */ '@/views/admin/library/AlbumsList' ), props: route => { return { @@ -352,20 +382,20 @@ export default new Router({ } }, { - path: "albums/:id", - name: "manage.library.albums.detail", + path: 'albums/:id', + name: 'manage.library.albums.detail', component: () => import( - /* webpackChunkName: "admin" */ "@/views/admin/library/AlbumDetail" + /* webpackChunkName: "admin" */ '@/views/admin/library/AlbumDetail' ), props: true }, { - path: "tracks", - name: "manage.library.tracks", + path: 'tracks', + name: 'manage.library.tracks', component: () => import( - /* webpackChunkName: "admin" */ "@/views/admin/library/TracksList" + /* webpackChunkName: "admin" */ '@/views/admin/library/TracksList' ), props: route => { return { @@ -374,20 +404,20 @@ export default new Router({ } }, { - path: "tracks/:id", - name: "manage.library.tracks.detail", + path: 'tracks/:id', + name: 'manage.library.tracks.detail', component: () => import( - /* webpackChunkName: "admin" */ "@/views/admin/library/TrackDetail" + /* webpackChunkName: "admin" */ '@/views/admin/library/TrackDetail' ), props: true }, { - path: "libraries", - name: "manage.library.libraries", + path: 'libraries', + name: 'manage.library.libraries', component: () => import( - /* webpackChunkName: "admin" */ "@/views/admin/library/LibrariesList" + /* webpackChunkName: "admin" */ '@/views/admin/library/LibrariesList' ), props: route => { return { @@ -396,20 +426,20 @@ export default new Router({ } }, { - path: "libraries/:id", - name: "manage.library.libraries.detail", + path: 'libraries/:id', + name: 'manage.library.libraries.detail', component: () => import( - /* webpackChunkName: "admin" */ "@/views/admin/library/LibraryDetail" + /* webpackChunkName: "admin" */ '@/views/admin/library/LibraryDetail' ), props: true }, { - path: "uploads", - name: "manage.library.uploads", + path: 'uploads', + name: 'manage.library.uploads', component: () => import( - /* webpackChunkName: "admin" */ "@/views/admin/library/UploadsList" + /* webpackChunkName: "admin" */ '@/views/admin/library/UploadsList' ), props: route => { return { @@ -418,20 +448,20 @@ export default new Router({ } }, { - path: "uploads/:id", - name: "manage.library.uploads.detail", + path: 'uploads/:id', + name: 'manage.library.uploads.detail', component: () => import( - /* webpackChunkName: "admin" */ "@/views/admin/library/UploadDetail" + /* webpackChunkName: "admin" */ '@/views/admin/library/UploadDetail' ), props: true }, { - path: "tags", - name: "manage.library.tags", + path: 'tags', + name: 'manage.library.tags', component: () => import( - /* webpackChunkName: "admin" */ "@/views/admin/library/TagsList" + /* webpackChunkName: "admin" */ '@/views/admin/library/TagsList' ), props: route => { return { @@ -440,67 +470,69 @@ export default new Router({ } }, { - path: "tags/:id", - name: "manage.library.tags.detail", + path: 'tags/:id', + name: 'manage.library.tags.detail', component: () => import( - /* webpackChunkName: "admin" */ "@/views/admin/library/TagDetail" + /* webpackChunkName: "admin" */ '@/views/admin/library/TagDetail' ), props: true } ] }, { - path: "/manage/users", + path: '/manage/users', + beforeEnter: adminPermissions, component: () => - import(/* webpackChunkName: "admin" */ "@/views/admin/users/Base"), + import(/* webpackChunkName: "admin" */ '@/views/admin/users/Base'), children: [ { - path: "users", - name: "manage.users.users.list", + path: 'users', + name: 'manage.users.users.list', component: () => import( - /* webpackChunkName: "admin" */ "@/views/admin/users/UsersList" + /* webpackChunkName: "admin" */ '@/views/admin/users/UsersList' ) }, { - path: "invitations", - name: "manage.users.invitations.list", + path: 'invitations', + name: 'manage.users.invitations.list', component: () => import( - /* webpackChunkName: "admin" */ "@/views/admin/users/InvitationsList" + /* webpackChunkName: "admin" */ '@/views/admin/users/InvitationsList' ) } ] }, { - path: "/manage/moderation", + path: '/manage/moderation', + beforeEnter: moderatorPermissions, component: () => - import(/* webpackChunkName: "admin" */ "@/views/admin/moderation/Base"), + import(/* webpackChunkName: "admin" */ '@/views/admin/moderation/Base'), children: [ { - path: "domains", - name: "manage.moderation.domains.list", + path: 'domains', + name: 'manage.moderation.domains.list', component: () => import( - /* webpackChunkName: "admin" */ "@/views/admin/moderation/DomainsList" + /* webpackChunkName: "admin" */ '@/views/admin/moderation/DomainsList' ) }, { - path: "domains/:id", - name: "manage.moderation.domains.detail", + path: 'domains/:id', + name: 'manage.moderation.domains.detail', component: () => import( - /* webpackChunkName: "admin" */ "@/views/admin/moderation/DomainsDetail" + /* webpackChunkName: "admin" */ '@/views/admin/moderation/DomainsDetail' ), props: true }, { - path: "accounts", - name: "manage.moderation.accounts.list", + path: 'accounts', + name: 'manage.moderation.accounts.list', component: () => import( - /* webpackChunkName: "admin" */ "@/views/admin/moderation/AccountsList" + /* webpackChunkName: "admin" */ '@/views/admin/moderation/AccountsList' ), props: route => { return { @@ -509,20 +541,20 @@ export default new Router({ } }, { - path: "accounts/:id", - name: "manage.moderation.accounts.detail", + path: 'accounts/:id', + name: 'manage.moderation.accounts.detail', component: () => import( - /* webpackChunkName: "admin" */ "@/views/admin/moderation/AccountsDetail" + /* webpackChunkName: "admin" */ '@/views/admin/moderation/AccountsDetail' ), props: true }, { - path: "reports", - name: "manage.moderation.reports.list", + path: 'reports', + name: 'manage.moderation.reports.list', component: () => import( - /* webpackChunkName: "admin" */ "@/views/admin/moderation/ReportsList" + /* webpackChunkName: "admin" */ '@/views/admin/moderation/ReportsList' ), props: route => { return { @@ -532,20 +564,20 @@ export default new Router({ } }, { - path: "reports/:id", - name: "manage.moderation.reports.detail", + path: 'reports/:id', + name: 'manage.moderation.reports.detail', component: () => import( - /* webpackChunkName: "admin" */ "@/views/admin/moderation/ReportDetail" + /* webpackChunkName: "admin" */ '@/views/admin/moderation/ReportDetail' ), props: true }, { - path: "requests", - name: "manage.moderation.requests.list", + path: 'requests', + name: 'manage.moderation.requests.list', component: () => import( - /* webpackChunkName: "admin" */ "@/views/admin/moderation/RequestsList" + /* webpackChunkName: "admin" */ '@/views/admin/moderation/RequestsList' ), props: route => { return { @@ -555,42 +587,42 @@ export default new Router({ } }, { - path: "requests/:id", - name: "manage.moderation.requests.detail", + path: 'requests/:id', + name: 'manage.moderation.requests.detail', component: () => import( - /* webpackChunkName: "admin" */ "@/views/admin/moderation/RequestDetail" + /* webpackChunkName: "admin" */ '@/views/admin/moderation/RequestDetail' ), props: true - }, + } ] }, { - path: "/library", + path: '/library', component: () => - import(/* webpackChunkName: "core" */ "@/components/library/Library"), + import(/* webpackChunkName: "core" */ '@/components/library/Library'), children: [ { - path: "", + path: '', component: () => - import(/* webpackChunkName: "core" */ "@/components/library/Home"), - name: "library.index" + import(/* webpackChunkName: "core" */ '@/components/library/Home'), + name: 'library.index' }, { - path: "me", + path: 'me', component: () => - import(/* webpackChunkName: "core" */ "@/components/library/Home"), - name: "library.me", + import(/* webpackChunkName: "core" */ '@/components/library/Home'), + name: 'library.me', props: route => ({ - scope: 'me', + scope: 'me' }) }, { - path: "artists/", - name: "library.artists.browse", + path: 'artists/', + name: 'library.artists.browse', component: () => import( - /* webpackChunkName: "artists" */ "@/components/library/Artists" + /* webpackChunkName: "artists" */ '@/components/library/Artists' ), props: route => ({ defaultOrdering: route.query.ordering, @@ -603,11 +635,11 @@ export default new Router({ }) }, { - path: "me/artists", - name: "library.artists.me", + path: 'me/artists', + name: 'library.artists.me', component: () => import( - /* webpackChunkName: "artists" */ "@/components/library/Artists" + /* webpackChunkName: "artists" */ '@/components/library/Artists' ), props: route => ({ scope: 'me', @@ -621,11 +653,11 @@ export default new Router({ }) }, { - path: "albums/", - name: "library.albums.browse", + path: 'albums/', + name: 'library.albums.browse', component: () => import( - /* webpackChunkName: "albums" */ "@/components/library/Albums" + /* webpackChunkName: "albums" */ '@/components/library/Albums' ), props: route => ({ defaultOrdering: route.query.ordering, @@ -638,11 +670,11 @@ export default new Router({ }) }, { - path: "podcasts/", - name: "library.podcasts.browse", + path: 'podcasts/', + name: 'library.podcasts.browse', component: () => import( - /* webpackChunkName: "podcasts" */ "@/components/library/Podcasts" + /* webpackChunkName: "podcasts" */ '@/components/library/Podcasts' ), props: route => ({ defaultOrdering: route.query.ordering, @@ -655,11 +687,11 @@ export default new Router({ }) }, { - path: "me/albums", - name: "library.albums.me", + path: 'me/albums', + name: 'library.albums.me', component: () => import( - /* webpackChunkName: "albums" */ "@/components/library/Albums" + /* webpackChunkName: "albums" */ '@/components/library/Albums' ), props: route => ({ scope: 'me', @@ -673,11 +705,11 @@ export default new Router({ }) }, { - path: "radios/", - name: "library.radios.browse", + path: 'radios/', + name: 'library.radios.browse', component: () => import( - /* webpackChunkName: "radios" */ "@/components/library/Radios" + /* webpackChunkName: "radios" */ '@/components/library/Radios' ), props: route => ({ defaultOrdering: route.query.ordering, @@ -687,11 +719,11 @@ export default new Router({ }) }, { - path: "me/radios/", - name: "library.radios.me", + path: 'me/radios/', + name: 'library.radios.me', component: () => import( - /* webpackChunkName: "radios" */ "@/components/library/Radios" + /* webpackChunkName: "radios" */ '@/components/library/Radios' ), props: route => ({ scope: 'me', @@ -702,35 +734,35 @@ export default new Router({ }) }, { - path: "radios/build", - name: "library.radios.build", + path: 'radios/build', + name: 'library.radios.build', component: () => import( - /* webpackChunkName: "radios" */ "@/components/library/radios/Builder" + /* webpackChunkName: "radios" */ '@/components/library/radios/Builder' ), props: true }, { - path: "radios/build/:id", - name: "library.radios.edit", + path: 'radios/build/:id', + name: 'library.radios.edit', component: () => import( - /* webpackChunkName: "radios" */ "@/components/library/radios/Builder" + /* webpackChunkName: "radios" */ '@/components/library/radios/Builder' ), props: true }, { - path: "radios/:id", - name: "library.radios.detail", + path: 'radios/:id', + name: 'library.radios.detail', component: () => - import(/* webpackChunkName: "radios" */ "@/views/radios/Detail"), + import(/* webpackChunkName: "radios" */ '@/views/radios/Detail'), props: true }, { - path: "playlists/", - name: "library.playlists.browse", + path: 'playlists/', + name: 'library.playlists.browse', component: () => - import(/* webpackChunkName: "playlists" */ "@/views/playlists/List"), + import(/* webpackChunkName: "playlists" */ '@/views/playlists/List'), props: route => ({ defaultOrdering: route.query.ordering, defaultQuery: route.query.query, @@ -739,10 +771,10 @@ export default new Router({ }) }, { - path: "me/playlists/", - name: "library.playlists.me", + path: 'me/playlists/', + name: 'library.playlists.me', component: () => - import(/* webpackChunkName: "playlists" */ "@/views/playlists/List"), + import(/* webpackChunkName: "playlists" */ '@/views/playlists/List'), props: route => ({ scope: 'me', defaultOrdering: route.query.ordering, @@ -752,190 +784,190 @@ export default new Router({ }) }, { - path: "playlists/:id", - name: "library.playlists.detail", + path: 'playlists/:id', + name: 'library.playlists.detail', component: () => - import(/* webpackChunkName: "playlists" */ "@/views/playlists/Detail"), + import(/* webpackChunkName: "playlists" */ '@/views/playlists/Detail'), props: route => ({ id: route.params.id, - defaultEdit: route.query.mode === "edit" + defaultEdit: route.query.mode === 'edit' }) }, { - path: "tags/:id", - name: "library.tags.detail", + path: 'tags/:id', + name: 'library.tags.detail', component: () => import( - /* webpackChunkName: "tags" */ "@/components/library/TagDetail" + /* webpackChunkName: "tags" */ '@/components/library/TagDetail' ), props: true }, { - path: "artists/:id", + path: 'artists/:id', component: () => import( - /* webpackChunkName: "artists" */ "@/components/library/ArtistBase" + /* webpackChunkName: "artists" */ '@/components/library/ArtistBase' ), props: true, children: [ { - path: "", - name: "library.artists.detail", + path: '', + name: 'library.artists.detail', component: () => import( - /* webpackChunkName: "artists" */ "@/components/library/ArtistDetail" + /* webpackChunkName: "artists" */ '@/components/library/ArtistDetail' ) }, { - path: "edit", - name: "library.artists.edit", + path: 'edit', + name: 'library.artists.edit', component: () => import( - /* webpackChunkName: "edits" */ "@/components/library/ArtistEdit" + /* webpackChunkName: "edits" */ '@/components/library/ArtistEdit' ) }, { - path: "edit/:editId", - name: "library.artists.edit.detail", + path: 'edit/:editId', + name: 'library.artists.edit.detail', component: () => import( - /* webpackChunkName: "edits" */ "@/components/library/EditDetail" + /* webpackChunkName: "edits" */ '@/components/library/EditDetail' ), props: true } ] }, { - path: "albums/:id", + path: 'albums/:id', component: () => import( - /* webpackChunkName: "albums" */ "@/components/library/AlbumBase" + /* webpackChunkName: "albums" */ '@/components/library/AlbumBase' ), props: true, children: [ { - path: "", - name: "library.albums.detail", + path: '', + name: 'library.albums.detail', component: () => import( - /* webpackChunkName: "albums" */ "@/components/library/AlbumDetail" + /* webpackChunkName: "albums" */ '@/components/library/AlbumDetail' ) }, { - path: "edit", - name: "library.albums.edit", + path: 'edit', + name: 'library.albums.edit', component: () => import( - /* webpackChunkName: "edits" */ "@/components/library/AlbumEdit" + /* webpackChunkName: "edits" */ '@/components/library/AlbumEdit' ) }, { - path: "edit/:editId", - name: "library.albums.edit.detail", + path: 'edit/:editId', + name: 'library.albums.edit.detail', component: () => import( - /* webpackChunkName: "edits" */ "@/components/library/EditDetail" + /* webpackChunkName: "edits" */ '@/components/library/EditDetail' ), props: true } ] }, { - path: "tracks/:id", + path: 'tracks/:id', component: () => import( - /* webpackChunkName: "tracks" */ "@/components/library/TrackBase" + /* webpackChunkName: "tracks" */ '@/components/library/TrackBase' ), props: true, children: [ { - path: "", - name: "library.tracks.detail", + path: '', + name: 'library.tracks.detail', component: () => import( - /* webpackChunkName: "tracks" */ "@/components/library/TrackDetail" + /* webpackChunkName: "tracks" */ '@/components/library/TrackDetail' ) }, { - path: "edit", - name: "library.tracks.edit", + path: 'edit', + name: 'library.tracks.edit', component: () => import( - /* webpackChunkName: "edits" */ "@/components/library/TrackEdit" + /* webpackChunkName: "edits" */ '@/components/library/TrackEdit' ) }, { - path: "edit/:editId", - name: "library.tracks.edit.detail", + path: 'edit/:editId', + name: 'library.tracks.edit.detail', component: () => import( - /* webpackChunkName: "edits" */ "@/components/library/EditDetail" + /* webpackChunkName: "edits" */ '@/components/library/EditDetail' ), props: true } ] }, { - path: "uploads/:id", - name: "library.uploads.detail", + path: 'uploads/:id', + name: 'library.uploads.detail', props: true, component: () => import( - /* webpackChunkName: "uploads" */ "@/components/library/UploadDetail" - ), + /* webpackChunkName: "uploads" */ '@/components/library/UploadDetail' + ) }, { // browse a single library via it's uuid - path: ":id([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})", + path: ':id([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})', props: true, component: () => import( - /* webpackChunkName: "library" */ "@/views/library/DetailBase" + /* webpackChunkName: "library" */ '@/views/library/DetailBase' ), children: [ { - path: "", - name: "library.detail", + path: '', + name: 'library.detail', component: () => import( - /* webpackChunkName: "library" */ "@/views/library/DetailOverview" + /* webpackChunkName: "library" */ '@/views/library/DetailOverview' ) }, { - path: "albums", - name: "library.detail.albums", + path: 'albums', + name: 'library.detail.albums', component: () => import( - /* webpackChunkName: "library" */ "@/views/library/DetailAlbums" + /* webpackChunkName: "library" */ '@/views/library/DetailAlbums' ) }, { - path: "tracks", - name: "library.detail.tracks", + path: 'tracks', + name: 'library.detail.tracks', component: () => import( - /* webpackChunkName: "library" */ "@/views/library/DetailTracks" + /* webpackChunkName: "library" */ '@/views/library/DetailTracks' ) }, { - path: "edit", - name: "library.detail.edit", + path: 'edit', + name: 'library.detail.edit', component: () => import( - /* webpackChunkName: "auth-libraries" */ "@/views/library/Edit" + /* webpackChunkName: "auth-libraries" */ '@/views/library/Edit' ) }, { - path: "upload", - name: "library.detail.upload", + path: 'upload', + name: 'library.detail.upload', component: () => import( - /* webpackChunkName: "auth-libraries" */ "@/views/library/Upload" + /* webpackChunkName: "auth-libraries" */ '@/views/library/Upload' ), props: route => ({ defaultImportReference: route.query.import }) - }, + } // { // path: "episodes", // name: "library.detail.episodes", @@ -949,34 +981,34 @@ export default new Router({ ] }, { - path: "/channels/:id", + path: '/channels/:id', props: true, component: () => import( - /* webpackChunkName: "channels" */ "@/views/channels/DetailBase" + /* webpackChunkName: "channels" */ '@/views/channels/DetailBase' ), children: [ { - path: "", - name: "channels.detail", + path: '', + name: 'channels.detail', component: () => import( - /* webpackChunkName: "channels" */ "@/views/channels/DetailOverview" + /* webpackChunkName: "channels" */ '@/views/channels/DetailOverview' ) }, { - path: "episodes", - name: "channels.detail.episodes", + path: 'episodes', + name: 'channels.detail.episodes', component: () => import( - /* webpackChunkName: "channels" */ "@/views/channels/DetailEpisodes" + /* webpackChunkName: "channels" */ '@/views/channels/DetailEpisodes' ) - }, + } ] }, { - path: "/subscriptions", - name: "subscriptions", + path: '/subscriptions', + name: 'subscriptions', props: route => { return { defaultQuery: route.query.q @@ -984,17 +1016,17 @@ export default new Router({ }, component: () => import( - /* webpackChunkName: "channels-auth" */ "@/views/channels/SubscriptionsList" - ), + /* webpackChunkName: "channels-auth" */ '@/views/channels/SubscriptionsList' + ) }, { - path: "*/index.html", - redirect: "/" + path: '*/index.html', + redirect: '/' }, { - path: "*", + path: '*', component: () => - import(/* webpackChunkName: "core" */ "@/components/PageNotFound") + import(/* webpackChunkName: "core" */ '@/components/PageNotFound') } ] }) diff --git a/front/src/store/auth.js b/front/src/store/auth.js index c8dfdc86c..a81866737 100644 --- a/front/src/store/auth.js +++ b/front/src/store/auth.js @@ -1,24 +1,22 @@ import Vue from 'vue' import axios from 'axios' import logger from '@/logging' -import router from '@/router' import lodash from '@/lodash' function getDefaultScopedTokens () { return { - listen: null, + listen: null } } function asForm (obj) { - let data = new FormData() + const data = new FormData() Object.entries(obj).forEach((e) => { data.set(e[0], e[1]) }) return data } - let baseUrl = `${window.location.protocol}//${window.location.hostname}` if (window.location.port) { baseUrl = `${baseUrl}:${window.location.port}` @@ -28,14 +26,14 @@ function getDefaultOauth () { clientId: null, clientSecret: null, accessToken: null, - refreshToken: null, + refreshToken: null } } const NEEDED_SCOPES = [ - "read", - "write", + 'read', + 'write' ].join(' ') -async function createOauthApp(domain) { +async function createOauthApp (domain) { const payload = { name: `Funkwhale web client at ${window.location.hostname}`, website: baseUrl, @@ -112,9 +110,9 @@ export default { state.token = value }, scopedTokens: (state, value) => { - state.scopedTokens = {...value} + state.scopedTokens = { ...value } }, - permission: (state, {key, status}) => { + permission: (state, { key, status }) => { state.availablePermissions[key] = status }, profilePartialUpdate: (state, payload) => { @@ -133,8 +131,9 @@ export default { }, actions: { // Send a request to the login URL and save the returned JWT - login ({commit, dispatch}, {next, credentials, onError}) { - var form = new FormData(); + login ({ commit, dispatch }, { next, credentials, onError }) { + const router = require('@/router').default + var form = new FormData() Object.keys(credentials).forEach((k) => { form.set(k, credentials[k]) }) @@ -150,13 +149,13 @@ export default { onError(response) }) }, - async logout ({state, commit}) { + async logout ({ state, commit }) { try { await axios.post('users/logout') } catch { console.log('Error while logging out, probably logged in via oauth') } - let modules = [ + const modules = [ 'auth', 'favorites', 'player', @@ -165,22 +164,21 @@ export default { 'radios' ] modules.forEach(m => { - commit(`${m}/reset`, null, {root: true}) + commit(`${m}/reset`, null, { root: true }) }) logger.default.info('Log out, goodbye!') }, - async check ({commit, dispatch, state}) { + async check ({ commit, dispatch, state }) { logger.default.info('Checking authentication…') commit('authenticated', false) - let profile = await dispatch('fetchProfile') + const profile = await dispatch('fetchProfile') if (profile) { commit('authenticated', true) } else { logger.default.info('Anonymous user') } }, - fetchProfile ({commit, dispatch, state}) { - + fetchProfile ({ commit, dispatch, state }) { return new Promise((resolve, reject) => { axios.get('users/me/').then((response) => { logger.default.info('Successfully fetched user profile') @@ -204,22 +202,22 @@ export default { dispatch('playlists/fetchOwn', null, { root: true }) }, (response) => { logger.default.info('Error while fetching user profile') - reject() + reject(new Error('Error while fetching user profile')) }) }) }, - updateProfile({ commit }, data) { + updateProfile ({ commit }, data) { return new Promise((resolve, reject) => { - commit("authenticated", true) - commit("profile", data) - commit("username", data.username) - commit("fullUsername", data.full_username) + commit('authenticated', true) + commit('profile', data) + commit('username', data.username) + commit('fullUsername', data.full_username) if (data.tokens) { - commit("scopedTokens", data.tokens) + commit('scopedTokens', data.tokens) } - Object.keys(data.permissions).forEach(function(key) { + Object.keys(data.permissions).forEach(function (key) { // this makes it easier to check for permissions in templates - commit("permission", { + commit('permission', { key, status: data.permissions[String(key)] }) @@ -227,45 +225,45 @@ export default { resolve() }) }, - async oauthLogin({ state, rootState, commit, getters }, next) { - let app = await createOauthApp(getters["appDomain"]) - commit("oauthApp", app) + async oauthLogin ({ state, rootState, commit, getters }, next) { + const app = await createOauthApp(getters.appDomain) + commit('oauthApp', app) const redirectUri = encodeURIComponent(`${baseUrl}/auth/callback`) - let params = `response_type=code&scope=${encodeURIComponent(NEEDED_SCOPES)}&redirect_uri=${redirectUri}&state=${next}&client_id=${state.oauth.clientId}` + const params = `response_type=code&scope=${encodeURIComponent(NEEDED_SCOPES)}&redirect_uri=${redirectUri}&state=${next}&client_id=${state.oauth.clientId}` const authorizeUrl = `${rootState.instance.instanceUrl}authorize?${params}` console.log('Redirecting user...', authorizeUrl) window.location = authorizeUrl }, - async handleOauthCallback({ state, commit, dispatch }, authorizationCode) { + async handleOauthCallback ({ state, commit, dispatch }, authorizationCode) { console.log('Fetching token...') const payload = { client_id: state.oauth.clientId, client_secret: state.oauth.clientSecret, - grant_type: "authorization_code", + grant_type: 'authorization_code', code: authorizationCode, redirect_uri: `${baseUrl}/auth/callback` } const response = await axios.post( 'oauth/token/', asForm(payload), - {headers: {'Content-Type': 'multipart/form-data'}} + { headers: { 'Content-Type': 'multipart/form-data' } } ) - commit("oauthToken", response.data) + commit('oauthToken', response.data) await dispatch('fetchProfile') }, - async refreshOauthToken({ state, commit }, authorizationCode) { + async refreshOauthToken ({ state, commit }, authorizationCode) { const payload = { client_id: state.oauth.clientId, client_secret: state.oauth.clientSecret, - grant_type: "refresh_token", - refresh_token: state.oauth.refreshToken, + grant_type: 'refresh_token', + refresh_token: state.oauth.refreshToken } - let response = await axios.post( - `oauth/token/`, + const response = await axios.post( + 'oauth/token/', asForm(payload), - {headers: {'Content-Type': 'multipart/form-data'}} + { headers: { 'Content-Type': 'multipart/form-data' } } ) commit('oauthToken', response.data) - }, + } } } From 7cf651943c866b75f4e92a40839b14a85d0c6844 Mon Sep 17 00:00:00 2001 From: Georg Krause Date: Fri, 30 Jul 2021 22:30:10 +0200 Subject: [PATCH 12/19] Follow best practice to label docker images following semantic versioning --- .gitlab-ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d0c609165..5e5834d09 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -228,10 +228,13 @@ docker_release: - (if [ "$CI_COMMIT_REF_NAME" == "develop" ] || [ "$CI_COMMIT_REF_NAME" == "master" ]; then ./scripts/set-api-build-metadata.sh $(echo $CI_COMMIT_SHA | cut -c 1-8); fi); script: - if [[ ! -z "$CI_COMMIT_TAG" ]]; then (./docs/get-releases-json.py | scripts/is-docker-latest.py $CI_COMMIT_TAG -) && export DOCKER_LATEST_TAG="-t $IMAGE_LATEST" || export DOCKER_LATEST_TAG=; fi + - if [[ "$CI_COMMIT_REF_NAME" =~ ^[0-9]+(.[0-9]+){1,2}$ ]]; then export stable=1 && export major="$(echo $CI_COMMIT_REF_NAME | cut -d '.' -f 1)" && export minor="$(echo $CI_COMMIT_REF_NAME | cut -d '.' -f 1,2)"; fi - cd api - docker build -t $IMAGE $DOCKER_LATEST_TAG . - docker push $IMAGE - if [[ ! -z "$DOCKER_LATEST_TAG" ]]; then docker push $IMAGE_LATEST; fi + - if [[ $stable == 1 ]]; then docker tag $IMAGE $IMAGE_NAME:major && docker push $IMAGE_NAME:major; fi + - if [[ $stable == 1 ]]; then docker tag $IMAGE $IMAGE_NAME:minor && docker push $IMAGE_NAME:minor; fi only: - develop@funkwhale/funkwhale - master@funkwhale/funkwhale From d72fd1829fe50293d28b9732e3ff0a34e766a0af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=20Gl=C3=A4=C3=9F-St=C3=B6cker?= Date: Sat, 31 Jul 2021 20:47:06 +0200 Subject: [PATCH 13/19] Use variables instead of strings --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5e5834d09..5c1ee3e17 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -233,8 +233,8 @@ docker_release: - docker build -t $IMAGE $DOCKER_LATEST_TAG . - docker push $IMAGE - if [[ ! -z "$DOCKER_LATEST_TAG" ]]; then docker push $IMAGE_LATEST; fi - - if [[ $stable == 1 ]]; then docker tag $IMAGE $IMAGE_NAME:major && docker push $IMAGE_NAME:major; fi - - if [[ $stable == 1 ]]; then docker tag $IMAGE $IMAGE_NAME:minor && docker push $IMAGE_NAME:minor; fi + - if [[ $stable == 1 ]]; then docker tag $IMAGE $IMAGE_NAME:$major && docker push $IMAGE_NAME:$major; fi + - if [[ $stable == 1 ]]; then docker tag $IMAGE $IMAGE_NAME:$minor && docker push $IMAGE_NAME:$minor; fi only: - develop@funkwhale/funkwhale - master@funkwhale/funkwhale From da6e7893acf9a65e202ba9265e77899035884b6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ciar=C3=A1n=20Ainsworth?= Date: Mon, 26 Jul 2021 18:54:14 +0000 Subject: [PATCH 14/19] Prevent open redirect on login --- front/src/router/index.js | 1 + front/src/views/auth/Login.vue | 22 ++++++++++++++++------ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/front/src/router/index.js b/front/src/router/index.js index 314bf5b1d..bd0c7c7cf 100644 --- a/front/src/router/index.js +++ b/front/src/router/index.js @@ -1025,6 +1025,7 @@ export default new Router({ }, { path: '*', + name: '404', component: () => import(/* webpackChunkName: "core" */ '@/components/PageNotFound') } diff --git a/front/src/views/auth/Login.vue b/front/src/views/auth/Login.vue index 3a5b076da..a63716555 100644 --- a/front/src/views/auth/Login.vue +++ b/front/src/views/auth/Login.vue @@ -3,30 +3,40 @@

Log in to your Funkwhale account

- +