Porównaj commity

...

394 Commity

Autor SHA1 Wiadomość Data
Ciarán Ainsworth ec368e0cd3 Update from attribute information
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2592>
2024-04-16 14:47:01 +02:00
Ciarán Ainsworth a2579bdc60 Add from attribute to genre tag spec
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2592>
2024-04-16 14:47:01 +02:00
Ciarán Ainsworth e1e0045a23 Add changelog fragment
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2592>
2024-04-16 14:47:01 +02:00
Ciarán Ainsworth 85c2be6a5b fix(docs): run pre-commit
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2592>
2024-04-16 14:47:01 +02:00
Ciarán Ainsworth 35de9bd48e feat(docs): add genre tags spec
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2592>
2024-04-16 14:47:01 +02:00
Petitminion ba5b657b61 lint
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2658>
2024-04-16 11:01:29 +00:00
Petitminion 4fc73c1430 lint
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2658>
2024-04-16 11:01:29 +00:00
Ciarán Ainsworth 97e24bcaa6 Apply 12 suggestion(s) to 4 file(s)
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2658>
2024-04-16 11:01:29 +00:00
Ciarán Ainsworth 1b15fea1ab Apply 1 suggestion(s) to 1 file(s)
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2658>
2024-04-16 11:01:29 +00:00
Ciarán Ainsworth b624fea2fa Apply 1 suggestion(s) to 1 file(s)
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2658>
2024-04-16 11:01:29 +00:00
Ciarán Ainsworth e028e8788b Apply 1 suggestion(s) to 1 file(s)
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2658>
2024-04-16 11:01:29 +00:00
Ciarán Ainsworth 67f74d40a6 Add ListenBrainz sync documentation
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2658>
2024-04-16 11:01:29 +00:00
Petitminion 547bd6f371 lint
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2658>
2024-04-16 11:01:29 +00:00
Petitminion 05ec6f6d0f tests
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2658>
2024-04-16 11:01:29 +00:00
Petitminion a03cc1db24 lint
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2658>
2024-04-16 11:01:29 +00:00
Petitminion 2a364d5785 add favorite sync
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2658>
2024-04-16 11:01:29 +00:00
Petitminion 5bc0171694 delete test
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2658>
2024-04-16 11:01:29 +00:00
Petitminion 37acfa475d loads of things
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2658>
2024-04-16 11:01:29 +00:00
Petitminion f45fd1e465 various reviews
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2658>
2024-04-16 11:01:29 +00:00
Petitminion 17c4a92f77 lint
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2658>
2024-04-16 11:01:29 +00:00
Petitminion 6414302899 implement listening and favorite sync with listenbrainz
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2658>
2024-04-16 11:01:29 +00:00
Ciarán Ainsworth 94a5b9e696
chore(deps): bump py3-pillow in Dockerfile 2024-04-14 15:32:26 +02:00
Bruno-Van-den-Bosch d673e77dff Translated using Weblate (Dutch)
Currently translated at 99.8% (2177 of 2181 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/nl/
2024-04-12 13:50:31 +00:00
Kasper Seweryn 02400ceea3 fix(types): resolve vuex and typescript >5 errors
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2769>
2024-02-29 11:03:38 +01:00
Kasper Seweryn 31f35a43f1 fix(eslint): update dependencies
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2769>
2024-02-29 09:39:48 +01:00
Renovate Bot 932de8c242 chore(front): update dependency @typescript-eslint/eslint-plugin to v7
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2769>
2024-02-27 09:33:42 +00:00
Renovate Bot a947a16b0f chore(api): update dependency watchdog to v4
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2768>
2024-02-26 14:03:48 +00:00
Renovate Bot a01079850d chore(api): update dependency faker to v23
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2767>
2024-02-26 12:06:16 +00:00
Ciarán Ainsworth 8d22eb925e Add API v2 overview
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2763>
2024-02-26 10:37:51 +01:00
Georg Krause 6fe153c8da refactor(api): Make sure CSRF_TRUSTED_ORIGIN always has a protocol prefix
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2709>
2024-02-26 07:44:18 +00:00
Georg Krause cb7284ef95 chore: Add changelog snippet
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2709>
2024-02-26 07:44:18 +00:00
Georg Krause 5ca8691feb test(api): Fix order of s3 backend initializartion
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2709>
2024-02-26 07:44:18 +00:00
Georg Krause b4920af0b8 fix(api): Replace deprecated is_ajax with manual check
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2709>
2024-02-26 07:44:18 +00:00
Georg Krause 803b077f00 chore: Update django api
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2709>
2024-02-26 07:44:18 +00:00
Georg Krause f1f6ef43ad chore: Replace reprecated alias django.conf.urls.urls()
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2709>
2024-02-26 07:44:18 +00:00
Georg Krause 0fd0192b37 chore: Replace deprecated smart_text with smart_str
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2709>
2024-02-26 07:44:18 +00:00
Georg Krause ac6d136105 chore: Remove deprecated argument for signal
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2709>
2024-02-26 07:44:18 +00:00
Georg Krause 4e825527a5 chore: Replace deprecated django.contrib.postgres.forms.JSONField with django.forms.JSONField
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2709>
2024-02-26 07:44:18 +00:00
Georg Krause 46ee53c967 chore: Use django.utils.translations.gettext_lazy instead of deprecated ugettext_lazy
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2709>
2024-02-26 07:44:18 +00:00
Georg Krause 765c801142 chore(api): Update dependency django to v4
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2709>
2024-02-26 07:44:18 +00:00
Tron e0e8a54d45 Translated using Weblate (Bengali)
Currently translated at 1.5% (33 of 2181 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/bn/
2024-02-25 09:50:31 +00:00
Tron c67884a245 Added translation using Weblate (Bengali) 2024-02-24 09:00:51 +00:00
Kasper Seweryn d2ca28ca47 fix(tests): fix localhost test
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2701>
2024-02-21 16:03:18 +01:00
Kasper Seweryn 30540ec186 fix: resolve rebase mistakes
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2701>
2024-02-21 15:47:30 +01:00
Kasper Seweryn 673fe8b828 feat: update dependencies
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2701>
2024-02-21 15:20:11 +01:00
Kasper Seweryn fe4af475af style(tauri): format the code with rustfmt
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2701>
2024-02-21 15:18:59 +01:00
Kasper Seweryn ad1bb6a220 feat(nix): remove flake
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2701>
2024-02-21 15:18:59 +01:00
Georg Krause 298ace1b72 feat(tauri): add metadata to Cargo.toml
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2701>
2024-02-21 15:18:59 +01:00
Georg Krause 37a1b008b3 ci: Upload Funkwhale Desktop AppImage into package registry
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2701>
2024-02-21 15:18:59 +01:00
Kasper Seweryn e42646d8a1 feat(instance-chooser): add dark mode support
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2701>
2024-02-21 15:18:59 +01:00
Kasper Seweryn 0095fc566e feat(tauri): offload OAuth login flow to a separate window
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2701>
2024-02-21 15:18:59 +01:00
Georg Krause 419da80e37 ci(tauri): Enable verbose logs
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2701>
2024-02-21 15:18:59 +01:00
Kasper Seweryn 0b99740d64 feat(appimage): bundle media framework
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2701>
2024-02-21 15:18:59 +01:00
Georg Krause 51f56bc808 chore: Add frontend artifacts to gitignore
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2701>
2024-02-21 15:18:59 +01:00
Georg Krause b00d782006 test(front): Install required dependencies for coverage calculation
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2701>
2024-02-21 15:07:12 +01:00
Kasper Seweryn f3a7394461 test: test if tauri env is recognized correctly
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2701>
2024-02-21 15:06:29 +01:00
Georg Krause cb8725a838 ci: Specify appimage path
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2701>
2024-02-21 14:48:45 +01:00
Georg Krause cddf6b9d93 ci: Add cargo bin to PATH
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2701>
2024-02-21 14:48:45 +01:00
Georg Krause 521c4d927c ci: Install dependencies before building tauri
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2701>
2024-02-21 14:48:45 +01:00
Kasper Seweryn 78329ca821 feat: update to tauri v2-beta.1
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2701>
2024-02-21 14:48:31 +01:00
Georg Krause 1ca5ea2b73 ci: Build tauri desktop app
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2701>
2024-02-21 14:46:10 +01:00
Kasper Seweryn 62f84a311b feat: enable switch instance button in tauri builds
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2701>
2024-02-21 14:46:10 +01:00
Kasper Seweryn 5bf6e23815 chore: add changelog snippet
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2701>
2024-02-21 14:46:10 +01:00
Kasper Seweryn 318aa196fa style: fix linting
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2701>
2024-02-21 14:46:10 +01:00
Kasper Seweryn b313d0e48c fix: remove unused locale key
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2701>
2024-02-21 14:46:10 +01:00
Kasper Seweryn cea9d9cf47 fix: fix some linting errors
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2701>
2024-02-21 14:46:10 +01:00
Kasper Seweryn 97aa045b0b feat: add pre-commit to shellHook
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2701>
2024-02-21 14:46:10 +01:00
Kasper Seweryn ccef0197c6 fix: fix locale path
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2701>
2024-02-21 14:46:09 +01:00
Kasper Seweryn 14d099b872 fix: fix tauri checks
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2701>
2024-02-21 14:46:06 +01:00
Kasper Seweryn 5647a1072d feat: add tauri
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2701>
2024-02-21 14:45:38 +01:00
Kasper Seweryn de232cb749 ci: Adjust coverage regex to also match int
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2757>
2024-02-21 10:13:32 +00:00
Georg Krause b1eba58dcc feat: add a type hint
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2757>
2024-02-21 08:34:57 +00:00
Georg Krause 06cfe8da95 ci: Report frontend test coverage to gitlab
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2757>
2024-02-21 08:24:45 +01:00
wvffle 6aa609970f fix(tests): don't wait arbitrary time
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2757>
2024-02-20 20:52:29 +00:00
wvffle 2b1228e620 fix(ci): ignore `afterall` in codespell
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2757>
2024-02-20 18:42:27 +00:00
wvffle 83120cced2 fix(tests): fix coverage in node tests
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2757>
2024-02-20 18:28:13 +00:00
wvffle 367ba84f13 fix(tests): replace serialize_upload with UploadSerializer
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2757>
2024-02-20 17:33:57 +00:00
wvffle 7957661573 style: fix linting errors
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2757>
2024-02-20 16:39:18 +00:00
wvffle 9e2d47f698 chore: add changelog snippets
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2757>
2024-02-20 14:45:33 +00:00
wvffle 243f2a57e3 test: add track cache tests and mock test server
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2757>
2024-02-20 14:39:55 +00:00
wvffle 670b522675 refactor: adjust code for lru-cache v10
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2757>
2024-02-20 14:31:55 +00:00
Renovate Bot ff6fc46c58 chore(front): update dependency lru-cache to v10
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2757>
2024-02-19 14:33:16 +00:00
Ciarán Ainsworth 84bb893f3a Remove deprecated flag for lychee
The --exclude-mail flag is deprecated and no longer needed

Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2759>
2024-02-18 18:27:36 +01:00
petitminion 6c38bae189 add MbidTaggedContent to nodeinfo (#2284) NOCHANGELOG 2024-02-16 09:57:31 +00:00
petitminion 4364d82b0b Add cli command to prune non mbid content from db (#2083) 2024-02-06 11:52:29 +00:00
Renovate Bot ac74380986 chore(front): update dependency jsdom to v24 2024-02-05 22:33:28 +00:00
Renovate Bot ee0abed0b7 chore(front): update dependency eslint-plugin-n to v16
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2755>
2024-02-05 22:03:38 +00:00
Renovate Bot fc456e6985 chore(front): update dependency dompurify to v3
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2753>
2024-02-05 20:33:30 +00:00
petitminion b0423d412f add prune mbid cli doc NOCHANGELOG 2024-02-05 20:25:17 +00:00
Renovate Bot 9853b89911 chore: update cypress/included docker tag to v13
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2752>
2024-02-05 15:59:42 +00:00
Renovate Bot e6e1b5cdc4 chore(front): update dependency cypress to v13
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2748>
2024-02-05 15:02:56 +00:00
Ciarán Ainsworth 3b45fde10a Re-add django dependencies
We use Django/django-environ to auto doc FW settings

Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2750>
2024-02-05 15:46:37 +01:00
Georg Krause 1eaad85c7d chore(docs) Update all dependencies
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2750>
2024-02-05 15:10:13 +01:00
Renovate Bot f76a797638 chore(front): update dependency vue-i18n to v9.9.1
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2749>
2024-02-05 12:04:13 +00:00
Georg Krause d7d6976229 feat(dev): Make neovim available in gitpod
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2734>
2024-02-05 08:34:13 +00:00
Renovate Bot 765bc62a2b chore(front): update dependency @vue/eslint-config-typescript to v12
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2746>
2024-02-04 23:33:21 +00:00
Renovate Bot 446b49fd46 chore(front): update dependency @vitejs/plugin-vue to v5
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2745>
2024-02-04 23:05:11 +00:00
Renovate Bot 0210304338 chore(front): update dependency @typescript-eslint/eslint-plugin to v6
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2744>
2024-02-04 22:33:38 +00:00
Renovate Bot 6d7a52c5ec chore(front): update dependency @intlify/unplugin-vue-i18n to v2
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2743>
2024-02-04 21:34:14 +00:00
Renovate Bot 825baecf8f chore(docs): update dependency sphinx-rtd-theme to v2
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2741>
2024-02-04 00:03:52 +00:00
Renovate Bot 62f7fda42c chore(api): update dependency watchdog to v3
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2736>
2024-02-02 19:34:37 +00:00
Georg Krause d82eceecae chore: Align with flake8 6.1 rules
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2737>
2024-02-02 19:46:08 +01:00
Renovate Bot f58a33ec02 chore: update pre-commit hook pycqa/flake8 to v6.1.0
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2737>
2024-02-02 13:03:59 +00:00
Renovate Bot abf0edfcdc chore(api): update dependency service-identity to v24
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2735>
2024-02-02 09:06:53 +00:00
Philipp Wolfer b658089e70 Subsonic: Note removal of "funkwhaleVersion" in FW 1.7.0
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2695>
2024-02-02 08:47:38 +00:00
Philipp Wolfer 82fdc82f93 Subsonic: Fixed getArtistInfo2 view test
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2695>
2024-02-02 08:47:38 +00:00
Philipp Wolfer 2371f2a4cb Subsonic: Added deprecation notice for funkwhaleVersion field
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2695>
2024-02-02 08:47:38 +00:00
Philipp Wolfer 136f24a917 Move Subsonic getArtistInfo2 serialization to serializer
Also fixed JSON serialization by not using lists for the single value fields.

Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2695>
2024-02-02 08:47:38 +00:00
Philipp Wolfer a5ee48818e Extend Subsonic XML renderer to allow explicit XML child tags
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2695>
2024-02-02 08:47:38 +00:00
Philipp Wolfer d227490f5b OpenSubsonic: report HTTP form POST extension as supported
Funkwhale already supports passing parameters as application/x-www-form-urlencoded

Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2695>
2024-02-02 08:47:38 +00:00
Philipp Wolfer bf8f1e41b9 OpenSubsonic: MBID for artist results, added mediaType field
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2695>
2024-02-02 08:47:38 +00:00
Philipp Wolfer e169e8edb1 Subsonic: Fixed casing of "bitRate" attribute
This follows the Subsonic / OpenSubsonic API spec

Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2695>
2024-02-02 08:47:38 +00:00
Philipp Wolfer 0fab0470c2 Subsonic: Actually implement getArtistInfo2 endpoint
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2695>
2024-02-02 08:47:38 +00:00
Philipp Wolfer 81401075aa Add OpenSubsonic support
Fixes #2270

Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2695>
2024-02-02 08:47:38 +00:00
Renovate Bot c1d91ce4d6 chore(api): update dependency redis to v5
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2733>
2024-02-02 01:37:00 +00:00
Renovate Bot 1f8c03e248 chore(api): update dependency pytest-sugar to v1
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2732>
2024-02-02 01:09:28 +00:00
Renovate Bot 42bf16034b chore(api): update dependency pytest-env to v1
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2731>
2024-02-02 00:04:24 +00:00
Renovate Bot 787acab3ab chore(api): update dependency pytest to v8
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2730>
2024-02-01 23:34:38 +00:00
Renovate Bot f43ef89c28 chore(api): update dependency pylint to v3
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2729>
2024-02-01 23:04:32 +00:00
Renovate Bot c4bec419ab chore(api): update dependency pycountry to v23
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2728>
2024-02-01 12:34:27 +00:00
Renovate Bot 55a4221b69 chore(api): update dependency gunicorn to v21
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2727>
2024-02-01 08:34:50 +00:00
Renovate Bot 60f66eea6d chore(api): update dependency faker to v22
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2725>
2024-02-01 03:07:54 +00:00
Renovate Bot 4148cdd186 chore(api): update dependency django-versatileimagefield to v3
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2724>
2024-02-01 02:06:23 +00:00
Renovate Bot 004d535eb7 chore(api): update dependency django-filter to v23
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2723>
2024-02-01 01:15:04 +00:00
Renovate Bot 132e291708 chore(api): update dependency django-debug-toolbar to v4
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2722>
2024-02-01 00:05:36 +00:00
Renovate Bot 40d2dcaeaf chore(api): update dependency django-cors-headers to v4
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2721>
2024-01-31 23:05:24 +00:00
Renovate Bot fa36c97d72 chore(api): update dependency django-cleanup to v8
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2720>
2024-01-31 22:04:12 +00:00
Renovate Bot 9b8828ca42 chore(api): update dependency django-cacheops to v7
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2719>
2024-01-31 21:04:25 +00:00
Georg Krause e0791b570f chore(api): Update dependency pillow to 10.2.0
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2689>
2024-01-31 14:15:22 +01:00
Georg Krause 90c9230a60 chore(api): Update dependencies to align with Alpine 3.19
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2689>
2024-01-30 16:45:44 +01:00
Renovate Bot 1e0f3abb54 chore(api): update alpine docker tag to v3.19
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2689>
2024-01-30 16:43:35 +01:00
Petitminion bfff1f85f9 make typesense task conditionnal
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2706>
2024-01-30 13:07:25 +00:00
petitminion ae9fea0cf1 implement pylistenbrainz NOCHANGELOG 2024-01-30 11:32:14 +00:00
Renovate Bot da370f5915 chore(api): update dependency bleach to v6
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2703>
2024-01-30 04:35:38 +00:00
Renovate Bot d6a078643b chore(api): update dependency coverage to v7
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2704>
2024-01-29 13:35:58 +00:00
Renovate Bot 7fcaa1fed2 chore(api): update dependency black to v24
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2702>
2024-01-29 09:07:47 +00:00
Georg Krause c3ae40cabe style: Cleanup script, remove unused functions
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2698>
2024-01-11 10:53:57 +01:00
Georg Krause daf9e80ca5 fix: Install zip in upstream image
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2698>
2024-01-11 10:53:57 +01:00
Georg Krause b4f18edaff fix: Remove variable overrides
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2698>
2024-01-11 10:53:57 +01:00
Georg Krause fa6d48f1b7 fix: Use correct auth header for package upload
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2698>
2024-01-11 10:53:57 +01:00
Georg Krause 8f3ab416ae ci: Remove creation of release, only publish packages
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2698>
2024-01-11 10:53:57 +01:00
jo cd9d6d696e ci: add release job
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2698>
2024-01-11 10:53:57 +01:00
Baudouin Feildel 2c90b32bb3 Add changelog entry.
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2699>
2024-01-10 11:09:31 +00:00
Baudouin Feildel e96748c029 Fix Apache configuration
Built assets are fetched using path like this: `/assets/foo-a1b2c3.js`. Apache failed to serve those, as it was missing disabling the proxy pass for the static assets folder. This commit adds the necessary configuration for properly serving the static assets.

Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2699>
2024-01-10 11:09:31 +00:00
Georg Krause d12ca2bad8 fix: Use the correct pre-defined variable to determine project namespace 2024-01-10 12:08:50 +01:00
Philipp Wolfer 332ae20f05 Fix docker dev setup
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2696>
2024-01-08 13:42:38 +00:00
Georg Krause 736625e235 ci: Don't run docker builds on foreign MRs 2024-01-08 14:40:26 +01:00
Georg Krause 33cd0f05a7 test(throttling): Explicitly enable throttling to make test more stable
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2670>
2024-01-03 10:02:08 +00:00
Georg Krause 06d135875b chore(api): Update dj-rest-auth to 5.0.2 and django-allauth to 0.55.2
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2670>
2024-01-03 10:02:03 +00:00
Bruno-Van-den-Bosch de41545ab3 Translated using Weblate (Dutch)
Currently translated at 99.6% (2174 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/nl/
2023-12-30 18:50:29 +00:00
Maksim Kliazovich 5ce00a9230 Added translation using Weblate (Belarusian) 2023-12-20 10:02:15 +00:00
Thomas d112d82768 Translated using Weblate (French)
Currently translated at 100.0% (9 of 9 strings)

Translation: Documentation/user-libraries-index
Translate-URL: https://translate.funkwhale.audio/projects/documentation/user-libraries-index/fr/
2023-12-18 13:38:28 +00:00
Thomas 03e9be77f9 Translated using Weblate (French)
Currently translated at 100.0% (4 of 4 strings)

Translation: Documentation/user-subsonic-index
Translate-URL: https://translate.funkwhale.audio/projects/documentation/user-subsonic-index/fr/
2023-12-18 13:38:28 +00:00
Thomas b6bcc88287 Translated using Weblate (French)
Currently translated at 11.1% (1 of 9 strings)

Translation: Documentation/administrator-upgrade-index
Translate-URL: https://translate.funkwhale.audio/projects/documentation/administrator-upgrade-index/fr/
2023-12-18 13:38:27 +00:00
Thomas 4677b9117d Translated using Weblate (French)
Currently translated at 13.3% (8 of 60 strings)

Translation: Documentation/administrator-installation-docker
Translate-URL: https://translate.funkwhale.audio/projects/documentation/administrator-installation-docker/fr/
2023-12-18 13:38:27 +00:00
Thomas bc573e47bc Translated using Weblate (French)
Currently translated at 100.0% (7 of 7 strings)

Translation: Documentation/administrator-installation-index
Translate-URL: https://translate.funkwhale.audio/projects/documentation/administrator-installation-index/fr/
2023-12-18 13:38:27 +00:00
Thomas 9a5a749171 Translated using Weblate (French)
Currently translated at 3.1% (1 of 32 strings)

Translation: Documentation/administrator-migration
Translate-URL: https://translate.funkwhale.audio/projects/documentation/administrator-migration/fr/
2023-12-18 13:38:27 +00:00
mittwerk de60ca7309 Translated using Weblate (Russian)
Currently translated at 100.0% (2182 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/ru/
2023-12-17 15:50:30 +00:00
josé m 5693d0f86d Translated using Weblate (Galician)
Currently translated at 100.0% (2182 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/gl/
2023-12-17 15:50:29 +00:00
Thomas 22084cbca7 Translated using Weblate (French)
Currently translated at 100.0% (2182 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/fr/
2023-12-15 13:50:28 +00:00
Georg Krause 731ee7c21e chore(api): Update kombu to 5.3.4 and celery to 5.3.6
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2660>
2023-12-13 14:34:54 +00:00
Georg Krause afea533aed chore(api): Update aiohttp to 3.9.1
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2660>
2023-12-13 14:33:58 +00:00
Georg Krause 8a6b19fb6f chore(api): Update Pillow to version 10.1.0
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2660>
2023-12-13 14:32:45 +00:00
Georg Krause 0eec47e493 feat(api): Add support for Python 3.12
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2660>
2023-12-13 14:30:29 +00:00
Georg Krause 4f9280bd2c ci: Run tests against python 3.12
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2660>
2023-12-13 14:29:04 +00:00
Renovate Bot 2ac4e25fce chore(api): update dependency ipython to v8
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2693>
2023-12-13 13:57:08 +00:00
Georg Krause 295b0dcc3a chore(renovate): Prioritize major over minor over patch updates in develop
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2690>
2023-12-13 13:52:33 +00:00
Ciarán Ainsworth ab0efa3edf Update behavior spec
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2631>
2023-12-13 13:46:15 +00:00
Ciarán Ainsworth 587bbc1118 fix(docs): use callout
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2631>
2023-12-13 13:46:15 +00:00
Ciarán Ainsworth b8978021c0 feat(docs): Add new upload process spec
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2631>
2023-12-13 13:46:15 +00:00
Georg Krause 349610bbeb chore: Use make install everywhere instead of poetry install
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2646>
2023-12-13 13:35:00 +00:00
Ciarán Ainsworth 65f13a379f Use glossary and clarify deletion process
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2630>
2023-12-12 16:15:44 +00:00
Ciarán Ainsworth ba53d03ac5 Add changelog for user deletion spec
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2630>
2023-12-12 16:15:44 +00:00
Ciarán Ainsworth cb65ee69e1 fix(docs): update heading and lexer
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2630>
2023-12-12 16:15:44 +00:00
Ciarán Ainsworth 65728c81c4 feat(docs): Add initial user deletion spec
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2630>
2023-12-12 16:15:44 +00:00
Matteo Piovanelli 5b022d94d1 Translated using Weblate (Italian)
Currently translated at 97.1% (2120 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/it/
2023-12-12 14:50:28 +00:00
Georg Krause 21ff5f65da Translated using Weblate (French)
Currently translated at 100.0% (2182 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/fr/
2023-12-12 14:50:28 +00:00
Georg Krause d8c734d3cd Translated using Weblate (Basque)
Currently translated at 100.0% (2182 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/eu/
2023-12-12 14:50:28 +00:00
Georg Krause b1f3a62fae Translated using Weblate (English (United Kingdom))
Currently translated at 100.0% (2182 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/en_GB/
2023-12-12 14:50:27 +00:00
Georg Krause 20cfaa8dc9 Translated using Weblate (German)
Currently translated at 100.0% (2182 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/de/
2023-12-12 14:50:27 +00:00
Georg Krause 038b696e75 Translated using Weblate (English)
Currently translated at 100.0% (2182 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/en/
2023-12-12 14:50:26 +00:00
Georg Krause 59687b2f32 Version bump and changelog for 1.4.0 2023-12-12 13:26:16 +01:00
Thomas da71fb640d Translated using Weblate (French)
Currently translated at 22.2% (2 of 9 strings)

Translation: Documentation/user-radios-index
Translate-URL: https://translate.funkwhale.audio/projects/documentation/user-radios-index/fr/
2023-12-11 21:34:10 +00:00
Thomas 09facc553d Translated using Weblate (French)
Currently translated at 100.0% (4 of 4 strings)

Translation: Documentation/user-reports-index
Translate-URL: https://translate.funkwhale.audio/projects/documentation/user-reports-index/fr/
2023-12-11 21:34:09 +00:00
Georg Krause da01070455 fix(nginx): Do not cache all requests for a day in the reverse proxy
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2673>
2023-12-11 14:41:16 +00:00
Georg Krause b00daa189d Translated using Weblate (Chinese (Simplified))
Currently translated at 95.8% (2091 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/zh_Hans/
2023-12-11 14:34:16 +00:00
drakonicguy aa0ce033aa Translated using Weblate (Polish)
Currently translated at 95.4% (2082 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/pl/
2023-12-11 14:34:16 +00:00
Georg Krause cc2272bb80 Translated using Weblate (Occitan)
Currently translated at 97.7% (2132 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/oc/
2023-12-11 14:34:15 +00:00
Matteo Piovanelli f0e79b4a0a Translated using Weblate (Italian)
Currently translated at 97.0% (2117 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/it/
2023-12-11 14:34:15 +00:00
Aznörth Niryn 9da91df798 Translated using Weblate (French)
Currently translated at 100.0% (2182 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/fr/
2023-12-11 14:34:15 +00:00
Aitor 807a6fd02c Translated using Weblate (Basque)
Currently translated at 100.0% (2182 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/eu/
2023-12-11 14:34:15 +00:00
Ciarán Ainsworth 517d99f9bf Translated using Weblate (English (United Kingdom))
Currently translated at 100.0% (2182 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/en_GB/
2023-12-11 14:34:14 +00:00
Georg Krause 6ab1dc0536 Translated using Weblate (German)
Currently translated at 100.0% (2182 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/de/
2023-12-11 14:34:10 +00:00
Georg Krause 803eb85b67 Translated using Weblate (English)
Currently translated at 100.0% (2182 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/en/
2023-12-11 14:34:09 +00:00
Georg Krause 6fcae233df Translated using Weblate (Russian)
Currently translated at 100.0% (2182 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/ru/
2023-12-10 11:45:17 +00:00
Georg Krause bf43b95208 Translated using Weblate (Galician)
Currently translated at 100.0% (2182 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/gl/
2023-12-10 11:45:16 +00:00
Georg Krause d721a3808b Translated using Weblate (French)
Currently translated at 100.0% (2182 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/fr/
2023-12-10 11:45:15 +00:00
Georg Krause d22a911619 Translated using Weblate (German)
Currently translated at 100.0% (2182 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/de/
2023-12-10 11:45:14 +00:00
Georg Krause 7c52227d43 Translated using Weblate (Czech)
Currently translated at 100.0% (2182 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/cs/
2023-12-10 11:45:14 +00:00
Georg Krause 58e2c896b2 Translated using Weblate (Catalan)
Currently translated at 100.0% (2182 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/ca/
2023-12-10 11:45:13 +00:00
Georg Krause 91b85cab46 Translated using Weblate (English)
Currently translated at 100.0% (2182 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/en/
2023-12-10 11:45:12 +00:00
Georg Krause bc15de7556 Translated using Weblate (German)
Currently translated at 98.9% (2158 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/de/
2023-12-10 10:42:05 +01:00
Georg Krause f99de1ef97 Translated using Weblate (English)
Currently translated at 100.0% (2182 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/en/
2023-12-10 10:42:05 +01:00
Georg Krause 5cc0219196 Added translation using Weblate (Bengali (Bangladesh)) 2023-12-10 10:42:05 +01:00
josé m 369b80bb1c Translated using Weblate (Galician)
Currently translated at 100.0% (2182 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/gl/
2023-12-10 10:42:05 +01:00
Thomas 60db27dfba Translated using Weblate (French)
Currently translated at 99.9% (2181 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/fr/
2023-12-10 10:42:05 +01:00
Aznörth Niryn efffeac280 Translated using Weblate (French)
Currently translated at 99.9% (2181 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/fr/
2023-12-10 10:42:05 +01:00
Thomas d112ea4bc6 Translated using Weblate (French)
Currently translated at 99.9% (2181 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/fr/
2023-12-10 10:42:05 +01:00
Aznörth Niryn b8ed2ccd5c Translated using Weblate (French)
Currently translated at 99.9% (2181 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/fr/
2023-12-10 10:42:05 +01:00
Quentin PAGÈS ab15803be0 Translated using Weblate (Occitan)
Currently translated at 97.8% (2135 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/oc/
2023-12-10 10:42:05 +01:00
Quentin PAGÈS e282422592 Translated using Weblate (Catalan)
Currently translated at 100.0% (2182 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/ca/
2023-12-10 10:42:05 +01:00
omarmaciasmolina 96d25ff25d Translated using Weblate (Catalan)
Currently translated at 100.0% (2182 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/ca/
2023-12-10 10:42:05 +01:00
rinenweb 8645180620 Translated using Weblate (Greek)
Currently translated at 39.9% (872 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/el/
2023-12-10 10:42:05 +01:00
Jérémie Lorente 142a517b93 Translated using Weblate (French)
Currently translated at 99.9% (2181 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/fr/
2023-12-10 10:42:05 +01:00
dignny 233d17d287 Translated using Weblate (Japanese)
Currently translated at 92.9% (2029 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/ja/
2023-12-10 10:42:05 +01:00
Aznörth Niryn 630ba7262a Translated using Weblate (French)
Currently translated at 99.9% (2181 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/fr/
2023-12-10 10:42:05 +01:00
dignny 0b78affdcd Translated using Weblate (Japanese)
Currently translated at 91.8% (2004 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/ja/
2023-12-10 10:42:05 +01:00
Transcriber allium 41dbf62356 Translated using Weblate (Greek)
Currently translated at 38.3% (836 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/el/
2023-12-10 10:42:05 +01:00
Matyáš Caras 6b6ba94291 Translated using Weblate (Czech)
Currently translated at 100.0% (2182 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/cs/
2023-12-10 10:42:05 +01:00
josé m 9eda066a39 Translated using Weblate (Galician)
Currently translated at 100.0% (2182 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/gl/
2023-12-10 10:42:05 +01:00
Aznörth Niryn 4cf2d68a4f Translated using Weblate (French)
Currently translated at 99.9% (2181 of 2182 strings)

Translation: Funkwhale/Funkwhale Web
Translate-URL: https://translate.funkwhale.audio/projects/funkwhale/front/fr/
2023-12-10 10:42:05 +01:00
Renovate Bot a19b459533 chore(front): update vue monorepo to v3.3.11
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2688>
2023-12-10 08:34:18 +00:00
Renovate Bot e3206e2122 chore(front): update dependency vue-router to v4.2.5
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2687>
2023-12-10 08:05:39 +00:00
Renovate Bot ba3300a682 chore(front): update dependency standardized-audio-context-mock to v9.6.32
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2686>
2023-12-09 21:05:29 +00:00
Renovate Bot c6aec56e71 chore(front): update dependency standardized-audio-context to v25.3.60
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2685>
2023-12-09 20:07:17 +00:00
Renovate Bot 02fd31d321 chore(front): update dependency @vue/eslint-config-typescript to v11.0.3
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2684>
2023-12-09 18:08:10 +00:00
Renovate Bot 07f665cb8b chore(front): update dependency @types/showdown to v2.0.6
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2683>
2023-12-09 17:35:36 +00:00
Renovate Bot 0b03bd6c89 chore(front): update dependency @types/semantic-ui to v2.2.9
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2682>
2023-12-09 17:09:31 +00:00
Renovate Bot 2aa301387c chore(front): update dependency @types/qs to v6.9.10
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2681>
2023-12-09 16:37:05 +00:00
Renovate Bot 46531884b3 chore(front): update dependency @types/moxios to v0.4.17
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2680>
2023-12-09 16:08:41 +00:00
Renovate Bot 6234dfd2a7 chore(front): update dependency @types/lodash-es to v4.17.12
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2679>
2023-12-09 15:36:10 +00:00
Renovate Bot 1c93460ffb chore(front): update dependency @types/jquery to v3.5.29
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2678>
2023-12-09 15:07:55 +00:00
Renovate Bot b6c906bf7c chore(front): update dependency @types/diff to v5.0.9
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2677>
2023-12-09 14:29:36 +00:00
Renovate Bot 793fc31e13 chore(docs): update dependency django to v3.2.23
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2676>
2023-12-09 14:09:43 +00:00
Georg Krause 80b4906438 chore(renovate): Disable automerge since it is prevented by our Gitlab settings 2023-12-09 13:55:51 +00:00
Renovate Bot e11a6cea02 chore(api): update dependency python-ldap to v3.4.4
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2674>
2023-12-09 11:17:48 +00:00
Renovate Bot b46aa638bc chore(api): update dependency unidecode to v1.3.7 2023-12-08 15:17:02 +00:00
Ciarán Ainsworth 17e08fd332 fix(docs): Update env file for Unix socket
Added note to the CACHE_URL variable to clarify Unix socket usage

Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2668>
2023-12-08 14:45:54 +00:00
Georg Krause 86ce4cfd7c fix(gitpod): Make sure jinja2 and towncrier are available
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2667>
2023-12-08 14:21:23 +00:00
Georg Krause b21e241f37 fix(gitpod): Properly serve media files, statics and fix proxy to API
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2667>
2023-12-08 14:21:23 +00:00
Renovate Bot 08bfc93243 chore(api): update dependency pylint-django to v2.5.5
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2671>
2023-12-06 09:35:39 +00:00
Ciarán Ainsworth 4cbce95bcb fix(docs): Fix postgres upgrade instructions
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2669>
2023-12-06 09:22:39 +00:00
Georg Krause 3ee6ba6658 fix(deploy): Serve staticfiles in bare metal deployments
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2665>
2023-12-05 20:05:44 +00:00
Thomas 259fb1b61d Translated using Weblate (French)
Currently translated at 8.6% (5 of 58 strings)

Translation: Documentation/user-channels-podcast-upload
Translate-URL: https://translate.funkwhale.audio/projects/documentation/user-channels-podcast-upload/fr/
2023-12-05 19:10:21 +00:00
Thomas 516c281a57 Translated using Weblate (French)
Currently translated at 8.6% (5 of 58 strings)

Translation: Documentation/user-channels-artist-upload
Translate-URL: https://translate.funkwhale.audio/projects/documentation/user-channels-artist-upload/fr/
2023-12-05 19:10:20 +00:00
Thomas d842243b3c Translated using Weblate (French)
Currently translated at 19.2% (5 of 26 strings)

Translation: Documentation/user-channels-podcast-delete
Translate-URL: https://translate.funkwhale.audio/projects/documentation/user-channels-podcast-delete/fr/
2023-12-05 19:10:20 +00:00
Thomas a4ea1a06b9 Translated using Weblate (French)
Currently translated at 19.2% (5 of 26 strings)

Translation: Documentation/user-channels-artist-delete
Translate-URL: https://translate.funkwhale.audio/projects/documentation/user-channels-artist-delete/fr/
2023-12-05 19:10:20 +00:00
Thomas d44c29bedb Translated using Weblate (French)
Currently translated at 16.6% (5 of 30 strings)

Translation: Documentation/user-channels-create
Translate-URL: https://translate.funkwhale.audio/projects/documentation/user-channels-create/fr/
2023-12-05 19:10:20 +00:00
Thomas 6e46660d70 Translated using Weblate (French)
Currently translated at 42.8% (6 of 14 strings)

Translation: Documentation/user-channels-delete
Translate-URL: https://translate.funkwhale.audio/projects/documentation/user-channels-delete/fr/
2023-12-05 19:10:20 +00:00
Thomas 32db5e92a3 Translated using Weblate (French)
Currently translated at 100.0% (4 of 4 strings)

Translation: Documentation/user-plugins-index
Translate-URL: https://translate.funkwhale.audio/projects/documentation/user-plugins-index/fr/
2023-12-05 19:10:19 +00:00
Thomas ba365d6722 Translated using Weblate (French)
Currently translated at 50.0% (6 of 12 strings)

Translation: Documentation/user-accounts-quota
Translate-URL: https://translate.funkwhale.audio/projects/documentation/user-accounts-quota/fr/
2023-12-05 19:10:19 +00:00
Thomas fd44d0bf12 Translated using Weblate (French)
Currently translated at 37.5% (6 of 16 strings)

Translation: Documentation/user-libraries-content-upload
Translate-URL: https://translate.funkwhale.audio/projects/documentation/user-libraries-content-upload/fr/
2023-12-05 19:10:19 +00:00
Thomas 70c0a038fc Translated using Weblate (French)
Currently translated at 50.0% (9 of 18 strings)

Translation: Documentation/user-libraries-content-delete
Translate-URL: https://translate.funkwhale.audio/projects/documentation/user-libraries-content-delete/fr/
2023-12-05 19:10:19 +00:00
Thomas 06e49598a3 Translated using Weblate (French)
Currently translated at 100.0% (15 of 15 strings)

Translation: Documentation/user-libraries-delete
Translate-URL: https://translate.funkwhale.audio/projects/documentation/user-libraries-delete/fr/
2023-12-05 19:10:19 +00:00
Thomas 779a3ee717 Translated using Weblate (French)
Currently translated at 27.2% (6 of 22 strings)

Translation: Documentation/user-libraries-create
Translate-URL: https://translate.funkwhale.audio/projects/documentation/user-libraries-create/fr/
2023-12-05 19:10:18 +00:00
Thomas 92f73b1755 Translated using Weblate (French)
Currently translated at 100.0% (21 of 21 strings)

Translation: Documentation/user-libraries-follow
Translate-URL: https://translate.funkwhale.audio/projects/documentation/user-libraries-follow/fr/
2023-12-05 19:10:18 +00:00
Thomas f34eb14c9a Translated using Weblate (French)
Currently translated at 36.8% (7 of 19 strings)

Translation: Documentation/user-libraries-share
Translate-URL: https://translate.funkwhale.audio/projects/documentation/user-libraries-share/fr/
2023-12-05 19:10:18 +00:00
Thomas 358ce509a5 Translated using Weblate (French)
Currently translated at 100.0% (18 of 18 strings)

Translation: Documentation/contributor-translation
Translate-URL: https://translate.funkwhale.audio/projects/documentation/contributor-translation/fr/
2023-12-05 19:10:17 +00:00
Thomas 65ebb8d90e Translated using Weblate (French)
Currently translated at 11.4% (4 of 35 strings)

Translation: Documentation/moderator-content-delete
Translate-URL: https://translate.funkwhale.audio/projects/documentation/moderator-content-delete/fr/
2023-12-05 19:10:17 +00:00
Thomas 499e1a8354 Translated using Weblate (French)
Currently translated at 2.5% (1 of 40 strings)

Translation: Documentation/developer-setup-gitpod
Translate-URL: https://translate.funkwhale.audio/projects/documentation/developer-setup-gitpod/fr/
2023-12-05 19:10:17 +00:00
Thomas 8de3c1489d Translated using Weblate (French)
Currently translated at 100.0% (17 of 17 strings)

Translation: Documentation/administrator-upgrade-debian
Translate-URL: https://translate.funkwhale.audio/projects/documentation/administrator-upgrade-debian/fr/
2023-12-05 19:10:16 +00:00
Thomas 11f7fa25ae Translated using Weblate (French)
Currently translated at 7.6% (2 of 26 strings)

Translation: Documentation/administrator-upgrade-docker
Translate-URL: https://translate.funkwhale.audio/projects/documentation/administrator-upgrade-docker/fr/
2023-12-05 19:10:16 +00:00
Thomas 1ccf18412f Translated using Weblate (French)
Currently translated at 100.0% (6 of 6 strings)

Translation: Documentation/administrator-configuration-index
Translate-URL: https://translate.funkwhale.audio/projects/documentation/administrator-configuration-index/fr/
2023-12-05 19:10:16 +00:00
Thomas 1061275487 Translated using Weblate (French)
Currently translated at 100.0% (30 of 30 strings)

Translation: Documentation/administrator-configuration-ldap
Translate-URL: https://translate.funkwhale.audio/projects/documentation/administrator-configuration-ldap/fr/
2023-12-05 19:10:15 +00:00
Thomas af592d99c2 Translated using Weblate (French)
Currently translated at 3.3% (1 of 30 strings)

Translation: Documentation/administrator-uninstall-debian
Translate-URL: https://translate.funkwhale.audio/projects/documentation/administrator-uninstall-debian/fr/
2023-12-05 19:10:14 +00:00
Thomas d1dd0bebcf Translated using Weblate (French)
Currently translated at 5.2% (1 of 19 strings)

Translation: Documentation/administrator-uninstall-docker
Translate-URL: https://translate.funkwhale.audio/projects/documentation/administrator-uninstall-docker/fr/
2023-12-05 19:10:13 +00:00
Renovate Bot 9da463e69d chore(api): update dependency pytest-env to v0.8.2
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2663>
2023-12-04 16:08:14 +00:00
Renovate Bot 1ee1c88ed1 chore(api): update dependency pytest to v7.4.3
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2664>
2023-12-04 14:34:30 +00:00
Renovate Bot e38808e2ce chore(api): update dependency pylint to v2.17.7
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2661>
2023-12-02 14:06:45 +00:00
Renovate Bot 2edbc6c98f chore(api): update dependency drf-spectacular to v0.26.5
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2657>
2023-12-02 13:28:39 +00:00
Georg Krause bfa50a0c35 chore: Add changelog snippet for ended support of Debian 10
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2656>
2023-12-02 13:22:42 +00:00
Georg Krause 74b2593cb2 Version bump and changelog for 1.4.0-rc2 2023-11-30 12:29:52 +01:00
Georg Krause cc2ff8ae88 ci: Use correct build arg to disable cache for docker builds
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2655>
2023-11-30 11:10:36 +00:00
Georg Krause 9dbbe9e768 fix(nginx): Use correct passing in production configs
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2654>
2023-11-29 09:58:17 +00:00
Georg Krause 0840aeb943 Version bump and changelog for 1.4.0-rc1 2023-11-28 19:35:15 +01:00
Georg Krause 362aa9db3e fix: Make sure all changelog snippets have the right ending 2023-11-28 19:28:47 +01:00
Georg Krause 150a9f68a4 fix(api): Use correct data field for rate limiting identity field
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2653>
2023-11-28 18:09:56 +00:00
Georg Krause 0c2f9c8dbb fix(nginx): Make sure pages that require OG tags are served by the backend
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2623>
2023-11-28 13:17:45 +00:00
Georg Krause 69876867d5 fix(embed): Make sure embed has sane default image and correct link
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2650>
2023-11-28 12:16:22 +00:00
Ciarán Ainsworth 76362b020e fix(nginx): fix websocket issue in template
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2648>
2023-11-27 11:32:39 +00:00
Ciarán Ainsworth b74a873b4a fix(docs): update broken link
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2649>
2023-11-26 19:46:51 +01:00
Renovate Bot dfb893e63b chore(api): update dependency aioresponses to v0.7.6
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2647>
2023-11-24 14:34:45 +00:00
Ciarán Ainsworth 4740df9d3c feat(docs): Move docstring and clarify debug
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2643>
2023-11-24 13:53:16 +00:00
Georg Krause 43c2861252 fix(api): Set logger to DEBUG if DEBUG is enabled
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2643>
2023-11-24 13:53:16 +00:00
Georg Krause 3db367f4bc feat(api): Add codeOfConduct to NodeInfo Endpoint
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2641>
2023-11-24 13:41:40 +00:00
Ciarán Ainsworth b6190540ee feat(docs): Added CoC property to nodeinfo spec
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2641>
2023-11-24 13:38:45 +00:00
Georg Krause 6157df5552 fix(nginx): Fix docker nginx configurations 2023-11-23 12:55:52 +00:00
Ciarán Ainsworth eb0c644b93 fix(front): Fix broken copy button in embed modal
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2642>
2023-11-23 12:10:38 +00:00
Ciarán Ainsworth 08c142cfff fix(front): Fixed regex order in embed
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2642>
2023-11-23 12:10:38 +00:00
Georg Krause a0ae9bbb70 feat(api): Add NodeInfo 2.1
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2604>
2023-11-23 11:47:03 +01:00
Georg Krause 71140d5a9b feat(settings): Allow to set the instances server location
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2604>
2023-11-23 11:47:03 +01:00
Georg Krause 1a0596b102 feat(settings): Allow moderators to set moderation languages
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2604>
2023-11-23 11:47:00 +01:00
Georg Krause 523245d035 fix(api): Use proper renderer for nodeinfo in browser
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2604>
2023-11-23 11:46:27 +01:00
Georg Krause a05b44f27b feat(api): Add atom1.0 to node info services
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2604>
2023-11-23 11:46:27 +01:00
Georg Krause e3a28aaeb3 chore(api): Remove obsolete file
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2604>
2023-11-23 11:46:27 +01:00
Renovate Bot dd4d191767 chore(front): lock file maintenance
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2640>
2023-11-23 10:32:30 +00:00
Ciarán Ainsworth 4cfa3a4f71 Fix semicolons
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2547>
2023-11-23 09:46:51 +00:00
Georg Krause 88d7bdb8ab feat(nginx): Generate configs using a template
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2547>
2023-11-23 09:46:51 +00:00
Georg Krause abf1306e2f feat(nginx): Use builtin envsubst mechanics of nginx container
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2547>
2023-11-23 09:46:51 +00:00
Georg Krause 346d4e9639 fix(api): Pin lb-matching-tools version
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2603>
2023-11-23 10:28:41 +01:00
Ciarán Ainsworth f769c8ce68 fix(tests): fix broken test
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2607>
2023-11-19 14:16:42 +00:00
Ciarán Ainsworth a7c76279f6 Apply 1 suggestion(s) to 1 file(s)
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2607>
2023-11-19 14:16:42 +00:00
Ciarán Ainsworth e2a0697529 feat(docs): Add MBID-only setting documentation
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2607>
2023-11-19 14:16:42 +00:00
Petitminion 7bf1d95d8e add test
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2607>
2023-11-19 14:16:42 +00:00
Petitminion 363a4b5d35 resolves review
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2607>
2023-11-19 14:16:42 +00:00
Petitminion ccb9987a95 lint
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2607>
2023-11-19 14:16:42 +00:00
Petitminion b6b0b22f6c typo
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2607>
2023-11-19 14:16:42 +00:00
Petitminion 179c53695e make setting dynamic
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2607>
2023-11-19 14:16:42 +00:00
Petitminion d3b27b4ba9 resolve test 2
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2607>
2023-11-19 14:16:42 +00:00
Petitminion 6dea3f3cf8 resolve test
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2607>
2023-11-19 14:16:42 +00:00
Petitminion 6e3185f653 changelog
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2607>
2023-11-19 14:16:42 +00:00
Petitminion df6f2d919d add common setting and lint
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2607>
2023-11-19 14:16:42 +00:00
Petitminion 2e3205a19d Only allow MusicBrainz tagged file on a pod (#2083)
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2607>
2023-11-19 14:16:42 +00:00
Ciarán Ainsworth 169cd69a46 fix(docs): use markdown content
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2634>
2023-11-18 16:58:08 +00:00
Ciarán Ainsworth 94c96e3045 fix(docs): fix rst formatting
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2634>
2023-11-18 16:58:08 +00:00
Ciarán Ainsworth b345d4d429 feat(docs): archived the pre-1.0 changelog
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2634>
2023-11-18 16:58:08 +00:00
Ciarán Ainsworth b11b0dfd52 fix(docs): update Docker steps in backup guide
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2549>
2023-11-18 16:51:33 +00:00
Alexander Dunkel 048b20130f fix chmod
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2549>
2023-11-18 16:51:33 +00:00
Alexander Dunkel 96b74d2984 Prettify
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2549>
2023-11-18 16:51:33 +00:00
Alexander Dunkel ce4b576b86 Fix chmod
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2549>
2023-11-18 16:51:33 +00:00
Alexander Dunkel 58fe1c4e57 docs: update command for postgres migrate
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2549>
2023-11-18 16:51:33 +00:00
Renovate Bot 739e5fa3b7 chore(api): update dependency aioresponses to v0.7.5
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2636>
2023-11-18 15:07:03 +00:00
Ciarán Ainsworth defc5931c6 fix: update link in README
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2635>
2023-11-18 14:46:23 +00:00
Ciarán Ainsworth 82a0a040d2 fix(docs): update website links in UI
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2635>
2023-11-18 14:46:23 +00:00
Ciarán Ainsworth 0a12fedaff fix(docs): make linter happy
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2629>
2023-11-16 11:25:25 +01:00
Ciarán Ainsworth e5bd8a0560 fix(docs): Clarify metadata object
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2629>
2023-11-16 10:18:12 +00:00
Ciarán Ainsworth 95c8e798ab fix(docs): Add nullable fields to Nodeinfo schema
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2629>
2023-11-16 10:18:12 +00:00
Ciarán Ainsworth 473cc1be25 Move information to metadata, reformat genres
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2629>
2023-11-16 10:18:12 +00:00
Ciarán Ainsworth 3d5381760f feat(docs): add usage statistics to nodeinfo specs
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2629>
2023-11-16 10:18:12 +00:00
Georg Krause 7ac6447308 Merge branch 'stable' into develop 2023-11-16 09:46:02 +00:00
Georg Krause 64b3fdf273 Version bump and changelog for 1.3.4 2023-11-16 09:33:49 +00:00
Renovate Bot 376e1fb019 chore(front): lock file maintenance
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2627>
2023-11-14 12:53:05 +00:00
Renovate Bot 65d36e59fa chore(docs): lock file maintenance
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2626>
2023-11-14 12:46:44 +00:00
Renovate Bot 3b287b1d37 chore(api): update dependency prompt-toolkit to v3.0.41
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2619>
2023-11-14 12:28:39 +00:00
Renovate Bot d0dc7d2232 chore(api): lock file maintenance
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2625>
2023-11-14 09:35:44 +00:00
Georg Krause 8f354135b5 ci(cypress): Switch to cypress/included image in order to have the binary
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2621>
2023-11-13 11:27:03 +01:00
Renovate Bot 28989d8ed6 chore(front): lock file maintenance
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2620>
2023-11-13 08:47:12 +00:00
Renovate Bot d6e5ba8acd chore(docs): pin dependency sphinx-autobuild to 2021.3.14
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2616>
2023-11-13 08:13:42 +00:00
Petitminion 4e79362aef remove network internal from typesense docker compose file
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2614>
2023-11-13 08:04:23 +00:00
Ciarán Ainsworth 18136c7ae4 fix(front): move library further up CSS imports
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2617>
2023-11-12 14:10:04 +01:00
Ciarán Ainsworth 7f12f5f9c3 chore: Add changelog snippet
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2617>
2023-11-12 12:45:45 +00:00
Ciarán Ainsworth 8f4251bb6e feat(front): Add support for Funkwhale UI library
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2617>
2023-11-12 12:45:45 +00:00
Georg Krause 66bd79b613 chore(gitpod): Pin python version to 3.11
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2605>
2023-11-12 12:28:31 +00:00
Georg Krause 1933a06cc0 feat(gitpod): Name the ports
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2605>
2023-11-12 12:28:31 +00:00
Georg Krause b05bce3b37 chore(gitpod): Speed up workspace initialisation
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2605>
2023-11-12 12:28:31 +00:00
Georg Krause debd334b38 chore(gitpod): Update workspace image to 2023-10-25-20-43-33
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2605>
2023-11-12 12:28:31 +00:00
Ciarán Ainsworth 935aa257b8 fix(docs): update public profile wording
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2610>
2023-11-12 12:20:10 +00:00
Ciarán Ainsworth 10ba5d02e7 chore(docs): Split up multi-artist spec
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2593>
2023-11-09 08:15:43 +00:00
Ciarán Ainsworth e120fc6815 feat(docs): add multi-artist spec
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2593>
2023-11-09 08:15:43 +00:00
Georg Krause a54522eac2 chore(renovate): Don't pin python version to the latest
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2594>
2023-11-09 07:35:53 +00:00
Renovate Bot 225d55924f chore(front): update dependency standardized-audio-context to v25.3.58
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2613>
2023-11-08 09:33:42 +00:00
Renovate Bot abb78a47e6 chore(api): update dependency django to v3.2.23
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2612>
2023-11-08 00:34:39 +00:00
Georg Krause 145ca4a1e7 ci(docker): Disable OCI mediatypes for buildx cache 2023-11-07 13:53:18 +01:00
Georg Krause ab73f355c0 fix: minor spelling issues
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2602>
2023-11-07 11:54:02 +00:00
Georg Krause 8485e4b162 fix(codespell): Don't falsely report a name as spelling mistake
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2602>
2023-11-07 09:54:50 +00:00
Ciarán Ainsworth 57ae3fae3c fix(docs): add missing has_mbid filter description
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2611>
2023-11-06 19:22:48 +00:00
Ciarán Ainsworth 0b91d0d7dc Revert "feat(docs): add user follow spec"
This reverts commit 384a4d1974
2023-11-06 19:13:48 +00:00
Ciarán Ainsworth 384a4d1974 feat(docs): add user follow spec 2023-11-06 19:12:18 +00:00
Ciarán Ainsworth 3a5090a85c feat(docs): update nodeinfo spec
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2609>
2023-11-06 11:46:08 +00:00
Renovate Bot cfb5850f7f chore: update pre-commit hook codespell-project/codespell to v2.2.6
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2602>
2023-11-06 10:33:57 +00:00
Georg Krause c69a48c457 docs: Fix redirect loop
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2608>
2023-11-05 11:01:30 +01:00
petitminion 7ccb2d88f8 Avoid troi radio to give duplicates (#2231) 2023-11-03 16:13:53 +00:00
Georg Krause 7ecd3e6767 chore(docs): Remove docker setup
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2348>
2023-11-02 12:04:20 +00:00
jooola bb1c6d935a chore: Remove logging handler
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2348>
2023-11-02 12:04:20 +00:00
Georg Krause 623d1571ee refactor(docs): Replace custom serve script with sphinx-autoreload
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2348>
2023-11-02 12:04:20 +00:00
Georg Krause a752a83ac0 feat(docs): Run documentation dev server in Gitpod
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2348>
2023-11-02 12:04:20 +00:00
Georg Krause 163e9310fc Update contributor documentation for the docs
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2348>
2023-11-02 12:04:20 +00:00
Georg Krause 58a5733987 fix(docs): Allow make to be run on MacOS
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2348>
2023-11-02 12:04:20 +00:00
Georg Krause 47efcb4b5a fix(docs): Use correct path configs
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2348>
2023-11-02 12:04:20 +00:00
Georg Krause e0f6641bba fix(docs): Make sure required directory exists
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2348>
2023-11-02 12:04:20 +00:00
Georg Krause 73364145c3 chore: Delete obsolete script
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2348>
2023-11-02 12:04:20 +00:00
jo fe47420ba1 docs: replace scripts with makefile
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2348>
2023-11-02 12:04:20 +00:00
jo c5dd88a2e2 chore: add new releases.py script
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2348>
2023-11-02 12:04:20 +00:00
Georg Krause accf261683 fix(gitpod): Remove falsely added pyenv commands 2023-11-02 10:03:56 +00:00
Georg Krause 9cd2f30129 chore: Avoid Python 3.12 since its not yet supported #2243 2023-11-01 15:03:03 +00:00
Mathieu Jourdan a756a5f920 collection terminology - insist on the possibility to follow as many collections as wanted
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2591>
2023-11-01 09:03:44 +00:00
Ciarán Ainsworth b70cabccdf Add changelog snippet
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2591>
2023-11-01 09:03:44 +00:00
Ciarán Ainsworth 1a04a84ec3 fix(docs): run pre-commit
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2591>
2023-11-01 09:03:44 +00:00
Ciarán Ainsworth c0d6c7ee74 feat(docs): Add collections spec
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2591>
2023-11-01 09:03:44 +00:00
Georg Krause 5eda0def09 test(s3): Verify the construction of audio file urls with custom s3 domain
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2577>
2023-10-27 17:58:03 +00:00
Georg Krause 40cc9afb65 test: Run tests with python 3.12
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2596>
2023-10-27 17:44:38 +00:00
Georg Krause 9d23d10e23 Adopt Gitpod to architecture changes 2023-10-13 10:12:05 +00:00
Renovate Bot ab7fe55b51 chore(api): update dependency psycopg2 to v2.9.9
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2600>
2023-10-12 12:35:13 +00:00
Renovate Bot ef827f22e5 chore(api): update dependency django to v3.2.22
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2599>
2023-10-12 12:05:22 +00:00
Renovate Bot 973ba97980 chore(api): update dependency aiohttp to v3.8.6
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2598>
2023-10-12 09:05:13 +00:00
alextprog ccec8288ef fix: Make Artist ordering by name case insensitive
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2431>
2023-10-10 13:02:13 +02:00
Georg Krause eae91ab016 fix: Remove dangling dependency howler
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2587>
2023-09-29 17:11:04 +00:00
Renovate Bot 2f1f7bcf95 chore: update pre-commit hook pre-commit/mirrors-prettier to v3.0.3
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2588>
2023-09-29 10:04:19 +00:00
Renovate Bot 433c9c78e8 chore(api): update dependency psycopg2 to v2.9.8
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2585>
2023-09-28 15:06:41 +00:00
Renovate Bot d9161a5088 chore(front): update dependency standardized-audio-context to v25.3.57
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2580>
2023-09-28 09:36:27 +00:00
Renovate Bot 3e9c0f80c6 chore(front): update dependency fomantic-ui-css to v2.9.3
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2579>
2023-09-14 04:36:21 +00:00
Renovate Bot cef09e877b chore(api): update dependency django to v3.2.21
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2578>
2023-09-13 09:11:44 +00:00
Renovate Bot 4ea74750ff chore(docs): lock file maintenance
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2571>
2023-09-13 08:49:31 +00:00
Renovate Bot 10b85fd638 chore(front): lock file maintenance
Part-of: <https://dev.funkwhale.audio/funkwhale/funkwhale/-/merge_requests/2572>
2023-09-07 06:35:53 +00:00
328 zmienionych plików z 72412 dodań i 42114 usunięć

Wyświetl plik

@ -7,7 +7,9 @@ nd
readby
serie
upto
afterall
# Names
nin
noe
manuel

12
.gitignore vendored
Wyświetl plik

@ -1,3 +1,5 @@
/dist
### OSX ###
.DS_Store
.AppleDouble
@ -83,8 +85,12 @@ front/yarn-debug.log*
front/yarn-error.log*
front/tests/unit/coverage
front/tests/e2e/reports
front/test_results.xml
front/coverage/
front/selenium-debug.log
docs/_build
#Tauri
front/tauri/gen
/data/
.env
@ -104,3 +110,9 @@ tsconfig.tsbuildinfo
# Vscode
.vscode/
# Nix
.direnv/
.envrc
flake.nix
flake.lock

Wyświetl plik

@ -118,21 +118,16 @@ review_docs:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
changes: [docs/**/*]
image: $CI_REGISTRY/funkwhale/ci/python-funkwhale-api:3.11
variables:
BUILD_PATH: "../docs-review"
image: $CI_REGISTRY/funkwhale/ci/python-funkwhale-docs:3.11
environment:
name: review/docs/$CI_COMMIT_REF_NAME
url: http://$CI_PROJECT_NAMESPACE.pages.funkwhale.audio/-/$CI_PROJECT_NAME/-/jobs/$CI_JOB_ID/artifacts/docs-review/index.html
cache: *docs_cache
before_script:
- mkdir docs-review
- cd docs
- apt-get update
- apt-get install -y graphviz
- poetry install
- make install
script:
- poetry run python3 -m sphinx . $BUILD_PATH
- make build BUILD_DIR=../docs-review
artifacts:
expire_in: 2 weeks
paths:
@ -149,7 +144,6 @@ find_broken_links:
--cache
--no-progress
--exclude-all-private
--exclude-mail
--exclude 'demo\.funkwhale\.audio'
--exclude 'nginx\.com'
--exclude-path 'docs/_templates/'
@ -236,7 +230,7 @@ test_api:
image: $CI_REGISTRY/funkwhale/ci/python-funkwhale-api:$PYTHON_VERSION
parallel:
matrix:
- PYTHON_VERSION: ["3.8", "3.9", "3.10", "3.11"]
- PYTHON_VERSION: ["3.8", "3.9", "3.10", "3.11", "3.12"]
services:
- name: postgres:15-alpine
command:
@ -253,7 +247,7 @@ test_api:
CACHE_URL: "redis://redis:6379/0"
before_script:
- cd api
- poetry install --all-extras
- make install
script:
- >
poetry run pytest
@ -293,6 +287,7 @@ test_front:
coverage_report:
coverage_format: cobertura
path: front/coverage/cobertura-coverage.xml
coverage: '/All files\s+(?:\|\s+((?:\d+\.)?\d+)\s+){4}.*/'
build_metadata:
stage: build
@ -317,7 +312,9 @@ test_integration:
- if: $RUN_CYPRESS
interruptible: true
image: cypress/base:18.12.1
image:
name: cypress/included:13.6.4
entrypoint: [""]
cache:
- *front_cache
- key:
@ -354,7 +351,7 @@ build_api_schema:
API_TYPE: "v1"
before_script:
- cd api
- poetry install --all-extras
- make install
- poetry run funkwhale-manage migrate
script:
- poetry run funkwhale-manage spectacular --file ../docs/schema.yml
@ -372,19 +369,13 @@ build_docs:
- if: $CI_COMMIT_BRANCH =~ /(stable|develop)/
- changes: [docs/**/*]
image: $CI_REGISTRY/funkwhale/ci/python-funkwhale-api:3.11
variables:
BUILD_PATH: "../public"
GIT_STRATEGY: clone
GIT_DEPTH: 0
image: $CI_REGISTRY/funkwhale/ci/python-funkwhale-docs:3.11
cache: *docs_cache
before_script:
- cd docs
- apt-get update
- apt-get install -y graphviz
- poetry install
- make install
script:
- ./build_docs.sh
- make build-all BUILD_DIR=../public
artifacts:
expire_in: 2 weeks
paths:
@ -439,6 +430,25 @@ build_api:
paths:
- api
build_tauri:
stage: build
rules:
- if: $CI_COMMIT_BRANCH =~ /(stable|develop)/
- changes: [front/**/*]
image: $CI_REGISTRY/funkwhale/ci/node-tauri:18
variables:
<<: *keep_git_files_permissions
before_script:
- source /root/.cargo/env
- yarn install
script:
- yarn tauri build --verbose
artifacts:
name: desktop_${CI_COMMIT_REF_NAME}
paths:
- front/tauri/target/release/bundle/appimage/*.AppImage
deploy_docs:
interruptible: false
extends: .ssh-agent
@ -471,22 +481,23 @@ docker:
variables:
BUILD_ARGS: >
--set *.platform=linux/amd64,linux/arm64,linux/arm/v7
--set *.no-cache
--no-cache
--push
- if: $CI_COMMIT_BRANCH =~ /(stable|develop)/
variables:
BUILD_ARGS: >
--set *.platform=linux/amd64,linux/arm64,linux/arm/v7
--set *.cache-from=type=registry,ref=$DOCKER_CACHE_IMAGE:$CI_COMMIT_BRANCH
--set *.cache-to=type=registry,ref=$DOCKER_CACHE_IMAGE:$CI_COMMIT_BRANCH,mode=max
--set *.cache-from=type=registry,ref=$DOCKER_CACHE_IMAGE:$CI_COMMIT_BRANCH,oci-mediatypes=false
--set *.cache-to=type=registry,ref=$DOCKER_CACHE_IMAGE:$CI_COMMIT_BRANCH,mode=max,oci-mediatypes=false
--push
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_PROJECT_NAMESPACE == "funkwhale"
# We don't provide priviledged runners to everyone, so we can only build docker images in the funkwhale group
variables:
BUILD_ARGS: >
--set *.platform=linux/amd64
--set *.cache-from=type=registry,ref=$DOCKER_CACHE_IMAGE:$CI_MERGE_REQUEST_TARGET_BRANCH_NAME
--set *.cache-from=type=registry,ref=$DOCKER_CACHE_IMAGE:$CI_MERGE_REQUEST_TARGET_BRANCH_NAME,oci-mediatypes=false
image: $CI_REGISTRY/funkwhale/ci/docker:20
services:
@ -517,3 +528,24 @@ docker:
name: docker_metadata_${CI_COMMIT_REF_NAME}
paths:
- metadata.json
package:
stage: publish
needs:
- job: build_metadata
artifacts: true
- job: build_api
artifacts: true
- job: build_front
artifacts: true
- job: build_tauri
artifacts: true
rules:
- if: $CI_COMMIT_BRANCH =~ /(stable|develop)/
image: $CI_REGISTRY/funkwhale/ci/python:3.11
variables:
<<: *keep_git_files_permissions
script:
- make package
- scripts/ci-upload-packages.sh

Wyświetl plik

@ -25,6 +25,16 @@
"branchConcurrentLimit": 0,
"prConcurrentLimit": 0
},
{
"matchBaseBranches": ["develop"],
"matchUpdateTypes": ["major"],
"prPriority": 2
},
{
"matchBaseBranches": ["develop"],
"matchUpdateTypes": ["minor"],
"prPriority": 1
},
{
"matchUpdateTypes": ["major", "minor"],
"matchBaseBranches": ["stable"],
@ -35,12 +45,6 @@
"matchBaseBranches": ["stable"],
"enabled": false
},
{
"matchUpdateTypes": ["patch", "pin", "digest"],
"matchBaseBranches": ["develop"],
"automerge": true,
"automergeType": "branch"
},
{
"matchManagers": ["npm"],
"addLabels": ["Area::Frontend"]
@ -70,6 +74,10 @@
],
"fileFilters": ["changes/changelog.d/postgres.update"]
}
},
{
"matchPackageNames": ["python"],
"rangeStrategy": "widen"
}
]
}

Wyświetl plik

@ -14,11 +14,12 @@ tasks:
docker-compose up -d
poetry env use python
poetry install
make install
gp ports await 5432
poetry run funkwhale-manage migrate
poetry run funkwhale-manage fw users create --superuser --username gitpod --password funkwhale --email test@example.org
poetry run funkwhale-manage gitpod init
command: |
echo "MEDIA_URL=`gp url 8000`/media/" >> ../.gitpod/.env
@ -47,49 +48,66 @@ tasks:
yarn install
command: yarn dev --host 0.0.0.0 --base ./
- name: Documentation
before: cd docs
init: make install
command: make dev
- name: Welcome to Funkwhale development!
env:
COMPOSE_FILE: /workspace/funkwhale/.gitpod/docker-compose.yml
ENV_FILE: /workspace/funkwhale/.gitpod/.env
VUE_EDITOR: code
DJANGO_SETTINGS_MODULE: config.settings.local
init: pre-commit install
init: |
pre-commit install
pre-commit run --all
command: |
pre-commit run --all && clear
echo ""
echo -e " ⠀⠀⠸⣿⣷⣦⣄⣠⣶⣾⣿⠇⠀⠀ You can now start developing Funkwhale with gitpod!"
echo -e " ⠀⠀⠀⠈⠉⠻⣿⣿⠟⠉⠁⠀⠀⠀"
echo -e " \u1b[34m⣀⢀⡀⢀⣀\u1b[0m⠹⠇\u1b[34m⣀⡀⢀⡀⣀ \u1b[0mTo sign in to the superuser account,"
echo -e " \u1b[34m⢻⣇⠘⣧⡈⠻⠶⠶⠟⢁⣾⠃⣸⡟ \u1b[0mplease use these credentials:"
echo -e " \u1b[34m⠻⣦⡈⠻⠶⣶⣶⠶⠟⢁⣴⠟"
echo -e " \u1b[34m⠈⠻⠷⣦⣤⣤⣴⠾⠟⠁ gitpod\u1b[0m:\u1b[34mgitpod"
echo -e " \u1b[34m⠈⠻⠷⣦⣤⣤⣴⠾⠟⠁ gitpod\u1b[0m:\u1b[34mfunkwhale"
echo ""
ports:
- port: 8000
- name: Funkwhale
port: 8000
visibility: public
onOpen: notify
- port: 5000
- name: Funkwhale API
port: 5000
visibility: private
onOpen: ignore
- port: 5432
- name: PostgreSQL
port: 5432
visibility: private
onOpen: ignore
- port: 5678
- name: Debugpy
port: 5678
visibility: private
onOpen: ignore
- port: 6379
- name: Redis
port: 6379
visibility: private
onOpen: ignore
- port: 8080
- name: Frontend
port: 8080
visibility: private
onOpen: ignore
- name: Documentation
port: 8001
visibility: public
onOpen: notify
vscode:
extensions:
- Vue.volar

Wyświetl plik

@ -1,9 +1,13 @@
FROM gitpod/workspace-full:2022-11-15-17-00-18
FROM gitpod/workspace-full:2023-10-25-20-43-33
USER gitpod
RUN sudo apt update -y \
&& sudo apt install libsasl2-dev libldap2-dev libssl-dev ffmpeg gettext -y
RUN pip install poetry pre-commit \
RUN pyenv install 3.11 && pyenv global 3.11
RUN brew install neovim
RUN pip install poetry pre-commit jinja2 towncrier \
&& poetry config virtualenvs.create true \
&& poetry config virtualenvs.in-project true

Wyświetl plik

@ -18,7 +18,6 @@ services:
- 6379:6379
nginx:
command: /entrypoint.sh
env_file:
- ./.env
image: nginx
@ -29,15 +28,16 @@ services:
environment:
- "NGINX_MAX_BODY_SIZE=100M"
- "FUNKWHALE_API_IP=host.docker.internal"
- "FUNKWHALE_API_HOST=host.docker.internal"
- "FUNKWHALE_API_PORT=5000"
- "FUNKWHALE_FRONT_IP=host.docker.internal"
- "FUNKWHALE_FRONT_PORT=8080"
- "FUNKWHALE_HOSTNAME=${FUNKWHALE_HOSTNAME-host.docker.internal}"
- "FUNKWHALE_PROTOCOL=https"
volumes:
- ../data/media:/protected/media:ro
- ../data/media:/workspace/funkwhale/data/media:ro
- ../data/music:/music:ro
- ../data/staticfiles:/staticfiles:ro
- ../data/staticfiles:/usr/share/nginx/html/staticfiles/:ro
- ../deploy/funkwhale_proxy.conf:/etc/nginx/funkwhale_proxy.conf:ro
- ../docker/nginx/conf.dev:/etc/nginx/nginx.conf.template:ro
- ../docker/nginx/entrypoint.sh:/entrypoint.sh:ro
- ../docker/nginx/conf.dev:/etc/nginx/templates/default.conf.template:ro
- ../front:/frontend:ro

Wyświetl plik

@ -53,18 +53,18 @@ repos:
- id: isort
- repo: https://github.com/pycqa/flake8
rev: 6.0.0
rev: 6.1.0
hooks:
- id: flake8
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v3.0.2
rev: v3.0.3
hooks:
- id: prettier
files: \.(md|yml|yaml|json)$
- repo: https://github.com/codespell-project/codespell
rev: v2.2.5
rev: v2.2.6
hooks:
- id: codespell
additional_dependencies: [tomli]

Plik diff jest za duży Load Diff

Wyświetl plik

@ -17,3 +17,41 @@ docker-build: docker-metadata
build-metadata:
./scripts/build_metadata.py --format env | tee build_metadata.env
BUILD_DIR = dist
package:
rm -Rf $(BUILD_DIR)
mkdir -p $(BUILD_DIR)
tar --create --gunzip --file='$(BUILD_DIR)/funkwhale-api.tar.gz' \
--owner='root' \
--group='root' \
--exclude-vcs \
api/config \
api/funkwhale_api \
api/install_os_dependencies.sh \
api/manage.py \
api/poetry.lock \
api/pyproject.toml \
api/Readme.md
cd '$(BUILD_DIR)' && \
tar --extract --gunzip --file='funkwhale-api.tar.gz' && \
zip -q 'funkwhale-api.zip' -r api && \
rm -Rf api
tar --create --gunzip --file='$(BUILD_DIR)/funkwhale-front.tar.gz' \
--owner='root' \
--group='root' \
--exclude-vcs \
--transform='s/^front\/dist/front/' \
front/dist
cd '$(BUILD_DIR)' && \
tar --extract --gunzip --file='funkwhale-front.tar.gz' && \
zip -q 'funkwhale-front.zip' -r front && \
rm -Rf front
cd '$(BUILD_DIR)' && \
cp ../front/tauri/target/release/bundle/appimage/funkwhale_*.AppImage FunkwhaleDesktop.AppImage
cd '$(BUILD_DIR)' && sha256sum * > SHA256SUMS

Wyświetl plik

@ -23,4 +23,4 @@ If you find a security issue or vulnerability, please report it on our [GitLab i
## Code of conduct
The Funkwhale collective adheres to a [code of conduct](https://funkwhale.audio/en_US/code-of-conduct) in all our community spaces. Please familiarize yourself with this code and follow it when participating in discussions in our spaces.
The Funkwhale collective adheres to a [code of conduct](https://funkwhale.audio/code-of-conduct) in all our community spaces. Please familiarize yourself with this code and follow it when participating in discussions in our spaces.

Wyświetl plik

@ -1,8 +1,4 @@
FROM alpine:3.17 as requirements
# We need this additional step to avoid having poetrys deps interacting with our
# dependencies. This is only required until alpine 3.16 is released, since this
# allows us to install poetry as package.
FROM alpine:3.19 as requirements
RUN set -eux; \
apk add --no-cache \
@ -16,7 +12,7 @@ RUN set -eux; \
poetry export --without-hashes --extras typesense > requirements.txt; \
poetry export --without-hashes --with dev > dev-requirements.txt;
FROM alpine:3.17 as builder
FROM alpine:3.19 as builder
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
@ -41,11 +37,11 @@ RUN set -eux; \
openssl-dev \
postgresql-dev \
zlib-dev \
py3-cryptography=38.0.3-r1 \
py3-cryptography=41.0.7-r0 \
py3-lxml=4.9.3-r1 \
py3-pillow=9.3.0-r0 \
py3-psycopg2=2.9.5-r0 \
py3-watchfiles=0.18.1-r0 \
py3-pillow=10.3.0-r0 \
py3-psycopg2=2.9.9-r0 \
py3-watchfiles=0.19.0-r1 \
python3-dev
# Create virtual env
@ -65,11 +61,11 @@ RUN --mount=type=cache,target=~/.cache/pip; \
# to install the deps using pip.
grep -Ev 'cryptography|lxml|pillow|psycopg2|watchfiles' /requirements.txt \
| pip3 install -r /dev/stdin \
cryptography==38.0.3 \
cryptography==41.0.7 \
lxml==4.9.3 \
pillow==9.3.0 \
psycopg2==2.9.5 \
watchfiles==0.18.1
pillow==10.2.0 \
psycopg2==2.9.9 \
watchfiles==0.19.0
ARG install_dev_deps=0
RUN --mount=type=cache,target=~/.cache/pip; \
@ -77,14 +73,14 @@ RUN --mount=type=cache,target=~/.cache/pip; \
if [ "$install_dev_deps" = "1" ] ; then \
grep -Ev 'cryptography|lxml|pillow|psycopg2|watchfiles' /dev-requirements.txt \
| pip3 install -r /dev/stdin \
cryptography==38.0.3 \
cryptography==41.0.7 \
lxml==4.9.3 \
pillow==9.3.0 \
psycopg2==2.9.5 \
watchfiles==0.18.1; \
pillow==10.2.0 \
psycopg2==2.9.9 \
watchfiles==0.19.0; \
fi
FROM alpine:3.17 as production
FROM alpine:3.19 as production
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
@ -101,11 +97,11 @@ RUN set -eux; \
libpq \
libxml2 \
libxslt \
py3-cryptography=38.0.3-r1 \
py3-cryptography=41.0.7-r0 \
py3-lxml=4.9.3-r1 \
py3-pillow=9.3.0-r0 \
py3-psycopg2=2.9.5-r0 \
py3-watchfiles=0.18.1-r0 \
py3-pillow=10.3.0-r0 \
py3-psycopg2=2.9.9-r0 \
py3-watchfiles=0.19.0-r1 \
python3 \
tzdata

Wyświetl plik

@ -4,7 +4,7 @@ CPU_CORES := $(shell N=$$(nproc); echo $$(( $$N > 4 ? 4 : $$N )))
.PHONY: install lint
install:
poetry install
poetry install --all-extras
lint:
poetry run pylint \

Wyświetl plik

@ -1,97 +0,0 @@
from django.conf.urls import include, url
from rest_framework import routers
from rest_framework.urlpatterns import format_suffix_patterns
from funkwhale_api.activity import views as activity_views
from funkwhale_api.audio import views as audio_views
from funkwhale_api.common import routers as common_routers
from funkwhale_api.common import views as common_views
from funkwhale_api.music import views
from funkwhale_api.playlists import views as playlists_views
from funkwhale_api.subsonic.views import SubsonicViewSet
from funkwhale_api.tags import views as tags_views
router = common_routers.OptionalSlashRouter()
router.register(r"activity", activity_views.ActivityViewSet, "activity")
router.register(r"tags", tags_views.TagViewSet, "tags")
router.register(r"plugins", common_views.PluginViewSet, "plugins")
router.register(r"tracks", views.TrackViewSet, "tracks")
router.register(r"uploads", views.UploadViewSet, "uploads")
router.register(r"libraries", views.LibraryViewSet, "libraries")
router.register(r"listen", views.ListenViewSet, "listen")
router.register(r"stream", views.StreamViewSet, "stream")
router.register(r"artists", views.ArtistViewSet, "artists")
router.register(r"channels", audio_views.ChannelViewSet, "channels")
router.register(r"subscriptions", audio_views.SubscriptionsViewSet, "subscriptions")
router.register(r"albums", views.AlbumViewSet, "albums")
router.register(r"licenses", views.LicenseViewSet, "licenses")
router.register(r"playlists", playlists_views.PlaylistViewSet, "playlists")
router.register(r"mutations", common_views.MutationViewSet, "mutations")
router.register(r"attachments", common_views.AttachmentViewSet, "attachments")
v1_patterns = router.urls
subsonic_router = routers.SimpleRouter(trailing_slash=False)
subsonic_router.register(r"subsonic/rest", SubsonicViewSet, basename="subsonic")
v1_patterns += [
url(r"^oembed/$", views.OembedView.as_view(), name="oembed"),
url(
r"^instance/",
include(("funkwhale_api.instance.urls", "instance"), namespace="instance"),
),
url(
r"^manage/",
include(("funkwhale_api.manage.urls", "manage"), namespace="manage"),
),
url(
r"^moderation/",
include(
("funkwhale_api.moderation.urls", "moderation"), namespace="moderation"
),
),
url(
r"^federation/",
include(
("funkwhale_api.federation.api_urls", "federation"), namespace="federation"
),
),
url(
r"^providers/",
include(("funkwhale_api.providers.urls", "providers"), namespace="providers"),
),
url(
r"^favorites/",
include(("funkwhale_api.favorites.urls", "favorites"), namespace="favorites"),
),
url(r"^search$", views.Search.as_view(), name="search"),
url(
r"^radios/",
include(("funkwhale_api.radios.urls", "radios"), namespace="radios"),
),
url(
r"^history/",
include(("funkwhale_api.history.urls", "history"), namespace="history"),
),
url(
r"^",
include(("funkwhale_api.users.api_urls", "users"), namespace="users"),
),
# XXX: remove if Funkwhale 1.1
url(
r"^users/",
include(("funkwhale_api.users.api_urls", "users"), namespace="users-nested"),
),
url(
r"^oauth/",
include(("funkwhale_api.users.oauth.urls", "oauth"), namespace="oauth"),
),
url(r"^rate-limit/?$", common_views.RateLimitView.as_view(), name="rate-limit"),
url(
r"^text-preview/?$", common_views.TextPreviewView.as_view(), name="text-preview"
),
]
urlpatterns = [
url(r"^v1/", include((v1_patterns, "v1"), namespace="v1"))
] + format_suffix_patterns(subsonic_router.urls, allowed=["view"])

Wyświetl plik

@ -303,6 +303,23 @@ LISTENING_CREATED = "listening_created"
"""
Called when a track is being listened
"""
LISTENING_SYNC = "listening_sync"
"""
Called by the task manager to trigger listening sync
"""
FAVORITE_CREATED = "favorite_created"
"""
Called when a track is being favorited
"""
FAVORITE_DELETED = "favorite_deleted"
"""
Called when a favorited track is being unfavorited
"""
FAVORITE_SYNC = "favorite_sync"
"""
Called by the task manager to trigger favorite sync
"""
SCAN = "scan"
"""

Wyświetl plik

@ -1,7 +1,7 @@
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from django.conf.urls import url
from django.core.asgi import get_asgi_application
from django.urls import re_path
from funkwhale_api.instance import consumers
@ -10,7 +10,12 @@ application = ProtocolTypeRouter(
# Empty for now (http->django views is added by default)
"websocket": AuthMiddlewareStack(
URLRouter(
[url("^api/v1/activity$", consumers.InstanceActivityConsumer.as_asgi())]
[
re_path(
"^api/v1/activity$",
consumers.InstanceActivityConsumer.as_asgi(),
)
]
)
),
"http": get_asgi_application(),

Wyświetl plik

@ -2,7 +2,7 @@ import logging.config
import sys
import warnings
from collections import OrderedDict
from urllib.parse import urlsplit
from urllib.parse import urlparse, urlsplit
import environ
from celery.schedules import crontab
@ -13,7 +13,29 @@ APPS_DIR = ROOT_DIR.path("funkwhale_api")
env = environ.Env()
ENV = env
LOGLEVEL = env("LOGLEVEL", default="info").upper()
# If DEBUG is `true`, we automatically set the loglevel to "DEBUG"
# If DEBUG is `false`, we try to read the level from LOGLEVEL environment and default to "INFO"
LOGLEVEL = (
"DEBUG" if env.bool("DEBUG", False) else env("LOGLEVEL", default="info").upper()
)
"""
Default logging level for the Funkwhale processes.
.. note::
The `DEBUG` variable overrides the `LOGLEVEL` if it is set to `TRUE`.
The `LOGLEVEL` value only applies if `DEBUG` is `false` or not present.
Available levels:
- ``debug``
- ``info``
- ``warning``
- ``error``
- ``critical``
"""
IS_DOCKER_SETUP = env.bool("IS_DOCKER_SETUP", False)
@ -35,19 +57,6 @@ if env("FUNKWHALE_SENTRY_DSN", default=None) is not None:
)
sentry_sdk.set_tag("instance", env("FUNKWHALE_HOSTNAME"))
"""
Default logging level for the Funkwhale processes
Available levels:
- ``debug``
- ``info``
- ``warning``
- ``error``
- ``critical``
""" # pylint: disable=W0105
LOGGING_CONFIG = None
logging.config.dictConfig(
{
@ -187,9 +196,7 @@ request errors related to this.
FUNKWHALE_SPA_HTML_CACHE_DURATION = env.int(
"FUNKWHALE_SPA_HTML_CACHE_DURATION", default=60 * 15
)
FUNKWHALE_EMBED_URL = env(
"FUNKWHALE_EMBED_URL", default=FUNKWHALE_URL + "/front/embed.html"
)
FUNKWHALE_EMBED_URL = env("FUNKWHALE_EMBED_URL", default=FUNKWHALE_URL + "/embed.html")
FUNKWHALE_SPA_REWRITE_MANIFEST = env.bool(
"FUNKWHALE_SPA_REWRITE_MANIFEST", default=True
)
@ -217,6 +224,13 @@ ALLOWED_HOSTS = env.list("DJANGO_ALLOWED_HOSTS", default=[]) + [FUNKWHALE_HOSTNA
List of allowed hostnames for which the Funkwhale server will answer.
"""
CSRF_TRUSTED_ORIGINS = [urlparse(o, FUNKWHALE_PROTOCOL).geturl() for o in ALLOWED_HOSTS]
"""
List of origins that are trusted for unsafe requests
We simply consider all allowed hosts to be trusted origins
See https://docs.djangoproject.com/en/4.2/ref/settings/#csrf-trusted-origins
"""
# APP CONFIGURATION
# ------------------------------------------------------------------------------
DJANGO_APPS = (
@ -262,6 +276,7 @@ LOCAL_APPS = (
# Your stuff: custom apps go here
"funkwhale_api.instance",
"funkwhale_api.audio",
"funkwhale_api.contrib.listenbrainz",
"funkwhale_api.music",
"funkwhale_api.requests",
"funkwhale_api.favorites",
@ -823,7 +838,7 @@ If you're using password auth (the extra slash is important)
.. note::
If you want to use Redis over unix sockets, you also need to update
:attr:`CELERY_BROKER_URL`, because the scheme differ from the one used by
:attr:`CELERY_BROKER_URL`, because the scheme differs from the one used by
:attr:`CACHE_URL`.
"""
@ -874,7 +889,7 @@ to use a different server or use Redis sockets to connect.
Example:
- ``redis://127.0.0.1:6379/0``
- ``unix://127.0.0.1:6379/0``
- ``redis+socket:///run/redis/redis.sock?virtual_host=0``
"""
@ -935,13 +950,25 @@ CELERY_BEAT_SCHEDULE = {
),
"options": {"expires": 60 * 60},
},
"typesense.build_canonical_index": {
"task": "typesense.build_canonical_index",
"schedule": crontab(day_of_week="*/2", minute="0", hour="3"),
"listenbrainz.trigger_listening_sync_with_listenbrainz": {
"task": "listenbrainz.trigger_listening_sync_with_listenbrainz",
"schedule": crontab(day_of_week="*", minute="0", hour="3"),
"options": {"expires": 60 * 60 * 24},
},
"listenbrainz.trigger_favorite_sync_with_listenbrainz": {
"task": "listenbrainz.trigger_favorite_sync_with_listenbrainz",
"schedule": crontab(day_of_week="*", minute="0", hour="3"),
"options": {"expires": 60 * 60 * 24},
},
}
if env.str("TYPESENSE_API_KEY", default=None):
CELERY_BEAT_SCHEDULE["typesense.build_canonical_index"] = {
"task": "typesense.build_canonical_index",
"schedule": crontab(day_of_week="*/2", minute="0", hour="3"),
"options": {"expires": 60 * 60 * 24},
}
if env.bool("ADD_ALBUM_TAGS_FROM_TRACKS", default=True):
CELERY_BEAT_SCHEDULE["music.albums_set_tags_from_tracks"] = {
"task": "music.albums_set_tags_from_tracks",
@ -1186,7 +1213,7 @@ if BROWSABLE_API_ENABLED:
"rest_framework.renderers.BrowsableAPIRenderer",
)
REST_AUTH_SERIALIZERS = {
REST_AUTH = {
"PASSWORD_RESET_SERIALIZER": "funkwhale_api.users.serializers.PasswordResetSerializer", # noqa
"PASSWORD_RESET_CONFIRM_SERIALIZER": "funkwhale_api.users.serializers.PasswordResetConfirmSerializer", # noqa
}

Wyświetl plik

@ -96,8 +96,6 @@ CELERY_TASK_ALWAYS_EAGER = False
# Your local stuff: Below this line define 3rd party library settings
CSRF_TRUSTED_ORIGINS = [o for o in ALLOWED_HOSTS]
REST_FRAMEWORK["DEFAULT_SCHEMA_CLASS"] = "funkwhale_api.schema.CustomAutoSchema"
SPECTACULAR_SETTINGS = {
"TITLE": "Funkwhale API",

Wyświetl plik

@ -41,14 +41,6 @@ SECRET_KEY = env("DJANGO_SECRET_KEY")
# SESSION_COOKIE_HTTPONLY = True
# SECURE_SSL_REDIRECT = env.bool("DJANGO_SECURE_SSL_REDIRECT", default=True)
# SITE CONFIGURATION
# ------------------------------------------------------------------------------
# Hosts/domain names that are valid for this site
# See https://docs.djangoproject.com/en/1.6/ref/settings/#allowed-hosts
CSRF_TRUSTED_ORIGINS = ALLOWED_HOSTS
# END SITE CONFIGURATION
# Static Assets
# ------------------------
STATICFILES_STORAGE = "django.contrib.staticfiles.storage.StaticFilesStorage"

Wyświetl plik

@ -1,7 +1,6 @@
from django.conf import settings
from django.conf.urls import url
from django.conf.urls.static import static
from django.urls import include, path
from django.urls import include, path, re_path
from django.views import defaults as default_views
from config import plugins
@ -10,34 +9,34 @@ from funkwhale_api.common import admin
plugins_patterns = plugins.trigger_filter(plugins.URLS, [], enabled=True)
api_patterns = [
url("v1/", include("config.urls.api")),
url("v2/", include("config.urls.api_v2")),
url("subsonic/", include("config.urls.subsonic")),
re_path("v1/", include("config.urls.api")),
re_path("v2/", include("config.urls.api_v2")),
re_path("subsonic/", include("config.urls.subsonic")),
]
urlpatterns = [
# Django Admin, use {% url 'admin:index' %}
url(settings.ADMIN_URL, admin.site.urls),
url(r"^api/", include((api_patterns, "api"), namespace="api")),
url(
re_path(settings.ADMIN_URL, admin.site.urls),
re_path(r"^api/", include((api_patterns, "api"), namespace="api")),
re_path(
r"^",
include(
("funkwhale_api.federation.urls", "federation"), namespace="federation"
),
),
url(r"^api/v1/auth/", include("funkwhale_api.users.rest_auth_urls")),
url(r"^accounts/", include("allauth.urls")),
re_path(r"^api/v1/auth/", include("funkwhale_api.users.rest_auth_urls")),
re_path(r"^accounts/", include("allauth.urls")),
] + plugins_patterns
if settings.DEBUG:
# This allows the error pages to be debugged during development, just visit
# these url in browser to see how these error pages look like.
urlpatterns += [
url(r"^400/$", default_views.bad_request),
url(r"^403/$", default_views.permission_denied),
url(r"^404/$", default_views.page_not_found),
url(r"^500/$", default_views.server_error),
re_path(r"^400/$", default_views.bad_request),
re_path(r"^403/$", default_views.permission_denied),
re_path(r"^404/$", default_views.page_not_found),
re_path(r"^500/$", default_views.server_error),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
if "debug_toolbar" in settings.INSTALLED_APPS:
@ -49,5 +48,5 @@ if settings.DEBUG:
if "silk" in settings.INSTALLED_APPS:
urlpatterns = [
url(r"^api/silk/", include("silk.urls", namespace="silk"))
re_path(r"^api/silk/", include("silk.urls", namespace="silk"))
] + urlpatterns

Wyświetl plik

@ -1,4 +1,5 @@
from django.conf.urls import include, url
from django.conf.urls import include
from django.urls import re_path
from funkwhale_api.activity import views as activity_views
from funkwhale_api.audio import views as audio_views
@ -28,61 +29,61 @@ router.register(r"attachments", common_views.AttachmentViewSet, "attachments")
v1_patterns = router.urls
v1_patterns += [
url(r"^oembed/$", views.OembedView.as_view(), name="oembed"),
url(
re_path(r"^oembed/$", views.OembedView.as_view(), name="oembed"),
re_path(
r"^instance/",
include(("funkwhale_api.instance.urls", "instance"), namespace="instance"),
),
url(
re_path(
r"^manage/",
include(("funkwhale_api.manage.urls", "manage"), namespace="manage"),
),
url(
re_path(
r"^moderation/",
include(
("funkwhale_api.moderation.urls", "moderation"), namespace="moderation"
),
),
url(
re_path(
r"^federation/",
include(
("funkwhale_api.federation.api_urls", "federation"), namespace="federation"
),
),
url(
re_path(
r"^providers/",
include(("funkwhale_api.providers.urls", "providers"), namespace="providers"),
),
url(
re_path(
r"^favorites/",
include(("funkwhale_api.favorites.urls", "favorites"), namespace="favorites"),
),
url(r"^search$", views.Search.as_view(), name="search"),
url(
re_path(r"^search$", views.Search.as_view(), name="search"),
re_path(
r"^radios/",
include(("funkwhale_api.radios.urls", "radios"), namespace="radios"),
),
url(
re_path(
r"^history/",
include(("funkwhale_api.history.urls", "history"), namespace="history"),
),
url(
re_path(
r"^",
include(("funkwhale_api.users.api_urls", "users"), namespace="users"),
),
# XXX: remove if Funkwhale 1.1
url(
re_path(
r"^users/",
include(("funkwhale_api.users.api_urls", "users"), namespace="users-nested"),
),
url(
re_path(
r"^oauth/",
include(("funkwhale_api.users.oauth.urls", "oauth"), namespace="oauth"),
),
url(r"^rate-limit/?$", common_views.RateLimitView.as_view(), name="rate-limit"),
url(
re_path(r"^rate-limit/?$", common_views.RateLimitView.as_view(), name="rate-limit"),
re_path(
r"^text-preview/?$", common_views.TextPreviewView.as_view(), name="text-preview"
),
]
urlpatterns = [url("", include((v1_patterns, "v1"), namespace="v1"))]
urlpatterns = [re_path("", include((v1_patterns, "v1"), namespace="v1"))]

Wyświetl plik

@ -1,4 +1,5 @@
from django.conf.urls import include, url
from django.conf.urls import include
from django.urls import re_path
from funkwhale_api.common import routers as common_routers
@ -6,14 +7,14 @@ router = common_routers.OptionalSlashRouter()
v2_patterns = router.urls
v2_patterns += [
url(
re_path(
r"^instance/",
include(("funkwhale_api.instance.urls", "instance"), namespace="instance"),
include(("funkwhale_api.instance.urls_v2", "instance"), namespace="instance"),
),
url(
re_path(
r"^radios/",
include(("funkwhale_api.radios.urls_v2", "radios"), namespace="radios"),
),
]
urlpatterns = [url("", include((v2_patterns, "v2"), namespace="v2"))]
urlpatterns = [re_path("", include((v2_patterns, "v2"), namespace="v2"))]

Wyświetl plik

@ -1,4 +1,5 @@
from django.conf.urls import include, url
from django.conf.urls import include
from django.urls import re_path
from rest_framework import routers
from rest_framework.urlpatterns import format_suffix_patterns
@ -8,7 +9,9 @@ subsonic_router = routers.SimpleRouter(trailing_slash=False)
subsonic_router.register(r"rest", SubsonicViewSet, basename="subsonic")
subsonic_patterns = format_suffix_patterns(subsonic_router.urls, allowed=["view"])
urlpatterns = [url("", include((subsonic_patterns, "subsonic"), namespace="subsonic"))]
urlpatterns = [
re_path("", include((subsonic_patterns, "subsonic"), namespace="subsonic"))
]
# urlpatterns = [
# url(

Wyświetl plik

@ -48,4 +48,5 @@ def get_activity(user, limit=20):
),
]
records = combined_recent(limit=limit, querysets=querysets)
return [r["object"] for r in records]

Wyświetl plik

@ -1,6 +1,6 @@
from allauth.account.utils import send_email_confirmation
from allauth.account.models import EmailAddress
from django.core.cache import cache
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from oauth2_provider.contrib.rest_framework.authentication import (
OAuth2Authentication as BaseOAuth2Authentication,
)
@ -20,9 +20,13 @@ def resend_confirmation_email(request, user):
if cache.get(cache_key):
return False
done = send_email_confirmation(request, user)
# We do the sending of the conformation by hand because we don't want to pass the request down
# to the email rendering, which would cause another UnverifiedEmail Exception and restarts the sending
# again and again
email = EmailAddress.objects.get_for_user(user, user.email)
email.send_confirmation()
cache.set(cache_key, True, THROTTLE_DELAY)
return done
return True
class OAuth2Authentication(BaseOAuth2Authentication):

Wyświetl plik

@ -1,5 +1,6 @@
from django import forms
from django.db.models import Q
from django.db.models.functions import Lower
from django_filters import rest_framework as filters
from django_filters import widgets
from drf_spectacular.utils import extend_schema_field
@ -239,3 +240,19 @@ class ActorScopeFilter(filters.CharFilter):
raise EmptyQuerySet()
return Q(**{self.actor_field: actor})
class CaseInsensitiveNameOrderingFilter(filters.OrderingFilter):
def filter(self, qs, value):
order_by = []
if value is None:
return qs
for param in value:
if param == "name":
order_by.append(Lower("name"))
else:
order_by.append(self.get_ordering_value(param))
return qs.order_by(*order_by)

Wyświetl plik

@ -36,22 +36,7 @@ class Command(BaseCommand):
self.stdout.write("")
def init(self):
try:
user = User.objects.get(username="gitpod")
except Exception:
call_command(
"createsuperuser",
username="gitpod",
email="gitpod@example.com",
no_input=False,
)
user = User.objects.get(username="gitpod")
user.set_password("gitpod")
if not user.actor:
user.create_actor()
user.save()
user = User.objects.get(username="gitpod")
# Allow anonymous access
preferences.set("common__api_authentication_required", False)

Wyświetl plik

@ -10,7 +10,7 @@ class Command(BaseCommand):
self.help = "Helper to generate randomized testdata"
self.type_choices = {"notifications": self.handle_notifications}
self.missing_args_message = f"Please specify one of the following sub-commands: { *self.type_choices.keys(), }"
self.missing_args_message = f"Please specify one of the following sub-commands: {*self.type_choices.keys(), }"
def add_arguments(self, parser):
subparsers = parser.add_subparsers(dest="subcommand")

Wyświetl plik

@ -150,7 +150,9 @@ def get_default_head_tags(path):
{
"tag": "meta",
"property": "og:image",
"content": utils.join_url(settings.FUNKWHALE_URL, "/front/favicon.png"),
"content": utils.join_url(
settings.FUNKWHALE_URL, "/android-chrome-512x512.png"
),
},
{
"tag": "meta",

Wyświetl plik

@ -60,12 +60,12 @@ class NullsLastSQLCompiler(SQLCompiler):
class NullsLastQuery(models.sql.query.Query):
"""Use a custom compiler to inject 'NULLS LAST' (for PostgreSQL)."""
def get_compiler(self, using=None, connection=None):
def get_compiler(self, using=None, connection=None, elide_empty=True):
if using is None and connection is None:
raise ValueError("Need either using or connection")
if using:
connection = connections[using]
return NullsLastSQLCompiler(self, connection, using)
return NullsLastSQLCompiler(self, connection, using, elide_empty)
class NullsLastQuerySet(models.QuerySet):

Wyświetl plik

@ -2,7 +2,7 @@ import json
from django import forms
from django.conf import settings
from django.contrib.postgres.forms import JSONField
from django.forms import JSONField
from dynamic_preferences import serializers, types
from dynamic_preferences.registries import global_preferences_registry
@ -93,7 +93,6 @@ class SerializedPreference(types.BasePreferenceType):
serializer
"""
serializer = JSONSerializer
data_serializer_class = None
field_class = JSONField
widget = forms.Textarea

Wyświetl plik

@ -5,8 +5,8 @@ import os
import PIL
from django.core.exceptions import ObjectDoesNotExist
from django.core.files.uploadedfile import SimpleUploadedFile
from django.utils.encoding import smart_text
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import smart_str
from django.utils.translation import gettext_lazy as _
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import extend_schema_field
from rest_framework import serializers
@ -52,7 +52,7 @@ class RelatedField(serializers.RelatedField):
self.fail(
"does_not_exist",
related_field_name=self.related_field_name,
value=smart_text(data),
value=smart_str(data),
)
except (TypeError, ValueError):
self.fail("invalid")
@ -349,7 +349,7 @@ class ScopesSerializer(serializers.Serializer):
class IdentSerializer(serializers.Serializer):
type = serializers.CharField()
id = serializers.IntegerField()
id = serializers.CharField()
class RateLimitSerializer(serializers.Serializer):

Wyświetl plik

@ -1,6 +1,6 @@
import django.dispatch
mutation_created = django.dispatch.Signal(providing_args=["mutation"])
mutation_updated = django.dispatch.Signal(
providing_args=["mutation", "old_is_approved", "new_is_approved"]
)
""" Required args: mutation """
mutation_created = django.dispatch.Signal()
""" Required args: mutation, old_is_approved, new_is_approved """
mutation_updated = django.dispatch.Signal()

Wyświetl plik

@ -7,7 +7,7 @@ from rest_framework import throttling as rest_throttling
def get_ident(user, request):
if user and user.is_authenticated:
return {"type": "authenticated", "id": user.pk}
return {"type": "authenticated", "id": f"{user.pk}"}
ident = rest_throttling.BaseThrottle().get_ident(request)
return {"type": "anonymous", "id": ident}

Wyświetl plik

@ -6,7 +6,7 @@ from django.core.exceptions import ValidationError
from django.core.files.images import get_image_dimensions
from django.template.defaultfilters import filesizeformat
from django.utils.deconstruct import deconstructible
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
@deconstructible

Wyświetl plik

@ -1,168 +0,0 @@
# Copyright (c) 2018 Philipp Wolfer <ph.wolfer@gmail.com>
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import json
import logging
import ssl
import time
from http.client import HTTPSConnection
HOST_NAME = "api.listenbrainz.org"
PATH_SUBMIT = "/1/submit-listens"
SSL_CONTEXT = ssl.create_default_context()
class Track:
"""
Represents a single track to submit.
See https://listenbrainz.readthedocs.io/en/latest/dev/json.html
"""
def __init__(self, artist_name, track_name, release_name=None, additional_info={}):
"""
Create a new Track instance
@param artist_name as str
@param track_name as str
@param release_name as str
@param additional_info as dict
"""
self.artist_name = artist_name
self.track_name = track_name
self.release_name = release_name
self.additional_info = additional_info
@staticmethod
def from_dict(data):
return Track(
data["artist_name"],
data["track_name"],
data.get("release_name", None),
data.get("additional_info", {}),
)
def to_dict(self):
return {
"artist_name": self.artist_name,
"track_name": self.track_name,
"release_name": self.release_name,
"additional_info": self.additional_info,
}
def __repr__(self):
return f"Track({self.artist_name}, {self.track_name})"
class ListenBrainzClient:
"""
Submit listens to ListenBrainz.org.
See https://listenbrainz.readthedocs.io/en/latest/dev/api.html
"""
def __init__(self, user_token, logger=logging.getLogger(__name__)):
self.__next_request_time = 0
self.user_token = user_token
self.logger = logger
def listen(self, listened_at, track):
"""
Submit a listen for a track
@param listened_at as int
@param entry as Track
"""
payload = _get_payload(track, listened_at)
return self._submit("single", [payload])
def playing_now(self, track):
"""
Submit a playing now notification for a track
@param track as Track
"""
payload = _get_payload(track)
return self._submit("playing_now", [payload])
def import_tracks(self, tracks):
"""
Import a list of tracks as (listened_at, Track) pairs
@param track as [(int, Track)]
"""
payload = _get_payload_many(tracks)
return self._submit("import", payload)
def _submit(self, listen_type, payload, retry=0):
self._wait_for_ratelimit()
self.logger.debug("ListenBrainz %s: %r", listen_type, payload)
data = {"listen_type": listen_type, "payload": payload}
headers = {
"Authorization": "Token %s" % self.user_token,
"Content-Type": "application/json",
}
body = json.dumps(data)
conn = HTTPSConnection(HOST_NAME, context=SSL_CONTEXT)
conn.request("POST", PATH_SUBMIT, body, headers)
response = conn.getresponse()
response_text = response.read()
try:
response_data = json.loads(response_text)
except json.decoder.JSONDecodeError:
response_data = response_text
self._handle_ratelimit(response)
log_msg = f"Response {response.status}: {response_data!r}"
if response.status == 429 and retry < 5: # Too Many Requests
self.logger.warning(log_msg)
return self._submit(listen_type, payload, retry + 1)
elif response.status == 200:
self.logger.debug(log_msg)
else:
self.logger.error(log_msg)
return response
def _wait_for_ratelimit(self):
now = time.time()
if self.__next_request_time > now:
delay = self.__next_request_time - now
self.logger.debug("Rate limit applies, delay %d", delay)
time.sleep(delay)
def _handle_ratelimit(self, response):
remaining = int(response.getheader("X-RateLimit-Remaining", 0))
reset_in = int(response.getheader("X-RateLimit-Reset-In", 0))
self.logger.debug("X-RateLimit-Remaining: %i", remaining)
self.logger.debug("X-RateLimit-Reset-In: %i", reset_in)
if remaining == 0:
self.__next_request_time = time.time() + reset_in
def _get_payload_many(tracks):
payload = []
for listened_at, track in tracks:
data = _get_payload(track, listened_at)
payload.append(data)
return payload
def _get_payload(track, listened_at=None):
data = {"track_metadata": track.to_dict()}
if listened_at is not None:
data["listened_at"] = listened_at
return data

Wyświetl plik

@ -1,27 +1,31 @@
import liblistenbrainz
import funkwhale_api
from config import plugins
from funkwhale_api.favorites import models as favorites_models
from funkwhale_api.history import models as history_models
from .client import ListenBrainzClient, Track
from . import tasks
from .funkwhale_startup import PLUGIN
@plugins.register_hook(plugins.LISTENING_CREATED, PLUGIN)
def submit_listen(listening, conf, **kwargs):
user_token = conf["user_token"]
if not user_token:
if not user_token and not conf["submit_listenings"]:
return
logger = PLUGIN["logger"]
logger.info("Submitting listen to ListenBrainz")
client = ListenBrainzClient(user_token=user_token, logger=logger)
track = get_track(listening.track)
client.listen(int(listening.creation_date.timestamp()), track)
client = liblistenbrainz.ListenBrainz()
client.set_auth_token(user_token)
listen = get_lb_listen(listening)
client.submit_single_listen(listen)
def get_track(track):
artist = track.artist.name
title = track.title
album = None
def get_lb_listen(listening):
track = listening.track
additional_info = {
"media_player": "Funkwhale",
"media_player_version": funkwhale_api.__version__,
@ -36,7 +40,7 @@ def get_track(track):
if track.album:
if track.album.title:
album = track.album.title
release_name = track.album.title
if track.album.mbid:
additional_info["release_mbid"] = str(track.album.mbid)
@ -47,4 +51,86 @@ def get_track(track):
if upload:
additional_info["duration"] = upload.duration
return Track(artist, title, album, additional_info)
return liblistenbrainz.Listen(
track_name=track.title,
artist_name=track.artist.name,
listened_at=listening.creation_date.timestamp(),
release_name=release_name,
additional_info=additional_info,
)
@plugins.register_hook(plugins.FAVORITE_CREATED, PLUGIN)
def submit_favorite_creation(track_favorite, conf, **kwargs):
user_token = conf["user_token"]
if not user_token or not conf["submit_favorites"]:
return
logger = PLUGIN["logger"]
logger.info("Submitting favorite to ListenBrainz")
client = liblistenbrainz.ListenBrainz()
track = track_favorite.track
if not track.mbid:
logger.warning(
"This tracks doesn't have a mbid. Feedback will not be submitted to Listenbrainz"
)
return
client.submit_user_feedback(1, track.mbid)
@plugins.register_hook(plugins.FAVORITE_DELETED, PLUGIN)
def submit_favorite_deletion(track_favorite, conf, **kwargs):
user_token = conf["user_token"]
if not user_token or not conf["submit_favorites"]:
return
logger = PLUGIN["logger"]
logger.info("Submitting favorite deletion to ListenBrainz")
client = liblistenbrainz.ListenBrainz()
track = track_favorite.track
if not track.mbid:
logger.warning(
"This tracks doesn't have a mbid. Feedback will not be submitted to Listenbrainz"
)
return
client.submit_user_feedback(0, track.mbid)
@plugins.register_hook(plugins.LISTENING_SYNC, PLUGIN)
def sync_listenings_from_listenbrainz(user, conf):
user_name = conf["user_name"]
if not user_name or not conf["sync_listenings"]:
return
logger = PLUGIN["logger"]
logger.info("Getting listenings from ListenBrainz")
try:
last_ts = (
history_models.Listening.objects.filter(user=user)
.filter(source="Listenbrainz")
.latest("creation_date")
.values_list("creation_date", flat=True)
).timestamp()
except funkwhale_api.history.models.Listening.DoesNotExist:
tasks.import_listenbrainz_listenings(user, user_name, 0)
return
tasks.import_listenbrainz_listenings(user, user_name, last_ts)
@plugins.register_hook(plugins.FAVORITE_SYNC, PLUGIN)
def sync_favorites_from_listenbrainz(user, conf):
user_name = conf["user_name"]
if not user_name or not conf["sync_favorites"]:
return
try:
last_ts = (
favorites_models.TrackFavorite.objects.filter(user=user)
.filter(source="Listenbrainz")
.latest("creation_date")
.creation_date.timestamp()
)
except favorites_models.TrackFavorite.DoesNotExist:
tasks.import_listenbrainz_favorites(user, user_name, 0)
return
tasks.import_listenbrainz_favorites(user, user_name, last_ts)

Wyświetl plik

@ -3,7 +3,7 @@ from config import plugins
PLUGIN = plugins.get_plugin_config(
name="listenbrainz",
label="ListenBrainz",
description="A plugin that allows you to submit your listens to ListenBrainz.",
description="A plugin that allows you to submit or sync your listens and favorites to ListenBrainz.",
homepage="https://docs.funkwhale.audio/users/builtinplugins.html#listenbrainz-plugin", # noqa
version="0.3",
user=True,
@ -13,6 +13,45 @@ PLUGIN = plugins.get_plugin_config(
"type": "text",
"label": "Your ListenBrainz user token",
"help": "You can find your user token in your ListenBrainz profile at https://listenbrainz.org/profile/",
}
},
{
"name": "user_name",
"type": "text",
"required": False,
"label": "Your ListenBrainz user name.",
"help": "Required for importing listenings and favorites with ListenBrainz \
but not to send activities",
},
{
"name": "submit_listenings",
"type": "boolean",
"default": True,
"label": "Enable listening submission to ListenBrainz",
"help": "If enabled, your listenings from Funkwhale will be imported into ListenBrainz.",
},
{
"name": "sync_listenings",
"type": "boolean",
"default": False,
"label": "Enable listenings sync",
"help": "If enabled, your listening from ListenBrainz will be imported into Funkwhale. This means they \
will be used along with Funkwhale listenings to filter out recently listened content or \
generate recommendations",
},
{
"name": "sync_favorites",
"type": "boolean",
"default": False,
"label": "Enable favorite sync",
"help": "If enabled, your favorites from ListenBrainz will be imported into Funkwhale. This means they \
will be used along with Funkwhale favorites (UI display, federation activity)",
},
{
"name": "submit_favorites",
"type": "boolean",
"default": False,
"label": "Enable favorite submission to ListenBrainz services",
"help": "If enabled, your favorites from Funkwhale will be submitted to ListenBrainz",
},
],
)

Wyświetl plik

@ -0,0 +1,165 @@
import datetime
import liblistenbrainz
from django.utils import timezone
from config import plugins
from funkwhale_api.favorites import models as favorites_models
from funkwhale_api.history import models as history_models
from funkwhale_api.music import models as music_models
from funkwhale_api.taskapp import celery
from funkwhale_api.users import models
from .funkwhale_startup import PLUGIN
@celery.app.task(name="listenbrainz.trigger_listening_sync_with_listenbrainz")
def trigger_listening_sync_with_listenbrainz():
now = timezone.now()
active_month = now - datetime.timedelta(days=30)
users = (
models.User.objects.filter(plugins__code="listenbrainz")
.filter(plugins__conf__sync_listenings=True)
.filter(last_activity__gte=active_month)
)
for user in users:
plugins.trigger_hook(
plugins.LISTENING_SYNC,
user=user,
confs=plugins.get_confs(user),
)
@celery.app.task(name="listenbrainz.trigger_favorite_sync_with_listenbrainz")
def trigger_favorite_sync_with_listenbrainz():
now = timezone.now()
active_month = now - datetime.timedelta(days=30)
users = (
models.User.objects.filter(plugins__code="listenbrainz")
.filter(plugins__conf__sync_listenings=True)
.filter(last_activity__gte=active_month)
)
for user in users:
plugins.trigger_hook(
plugins.FAVORITE_SYNC,
user=user,
confs=plugins.get_confs(user),
)
@celery.app.task(name="listenbrainz.import_listenbrainz_listenings")
def import_listenbrainz_listenings(user, user_name, since):
client = liblistenbrainz.ListenBrainz()
response = client.get_listens(username=user_name, min_ts=since, count=100)
listens = response["payload"]["listens"]
while listens:
add_lb_listenings_to_db(listens, user)
new_ts = max(
listens,
key=lambda obj: datetime.datetime.fromtimestamp(
obj.listened_at, timezone.utc
),
)
response = client.get_listens(username=user_name, min_ts=new_ts, count=100)
listens = response["payload"]["listens"]
def add_lb_listenings_to_db(listens, user):
logger = PLUGIN["logger"]
fw_listens = []
for listen in listens:
if (
listen.additional_info.get("submission_client")
and listen.additional_info.get("submission_client")
== "Funkwhale ListenBrainz plugin"
and history_models.Listening.objects.filter(
creation_date=datetime.datetime.fromtimestamp(
listen.listened_at, timezone.utc
)
).exists()
):
logger.info(
f"Listen with ts {listen.listened_at} skipped because already in db"
)
continue
mbid = (
listen.mbid_mapping
if hasattr(listen, "mbid_mapping")
else listen.recording_mbid
)
if not mbid:
logger.info("Received listening that doesn't have a mbid. Skipping...")
try:
track = music_models.Track.objects.get(mbid=mbid)
except music_models.Track.DoesNotExist:
logger.info(
"Received listening that doesn't exist in fw database. Skipping..."
)
continue
user = user
fw_listen = history_models.Listening(
creation_date=datetime.datetime.fromtimestamp(
listen.listened_at, timezone.utc
),
track=track,
user=user,
source="Listenbrainz",
)
fw_listens.append(fw_listen)
history_models.Listening.objects.bulk_create(fw_listens)
@celery.app.task(name="listenbrainz.import_listenbrainz_favorites")
def import_listenbrainz_favorites(user, user_name, since):
client = liblistenbrainz.ListenBrainz()
response = client.get_user_feedback(username=user_name)
offset = 0
while response["feedback"]:
count = response["count"]
offset = offset + count
last_sync = min(
response["feedback"],
key=lambda obj: datetime.datetime.fromtimestamp(
obj["created"], timezone.utc
),
)["created"]
add_lb_feedback_to_db(response["feedback"], user)
if last_sync <= since or count == 0:
return
response = client.get_user_feedback(username=user_name, offset=offset)
def add_lb_feedback_to_db(feedbacks, user):
logger = PLUGIN["logger"]
for feedback in feedbacks:
try:
track = music_models.Track.objects.get(mbid=feedback["recording_mbid"])
except music_models.Track.DoesNotExist:
logger.info(
"Received feedback track that doesn't exist in fw database. Skipping..."
)
continue
if feedback["score"] == 1:
favorites_models.TrackFavorite.objects.get_or_create(
user=user,
creation_date=datetime.datetime.fromtimestamp(
feedback["created"], timezone.utc
),
track=track,
source="Listenbrainz",
)
elif feedback["score"] == 0:
try:
favorites_models.TrackFavorite.objects.get(
user=user, track=track
).delete()
except favorites_models.TrackFavorite.DoesNotExist:
continue
elif feedback["score"] == -1:
logger.info("Funkwhale doesn't support disliked tracks")

Wyświetl plik

@ -0,0 +1,18 @@
# Generated by Django 3.2.20 on 2023-12-09 14:25
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('favorites', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='trackfavorite',
name='source',
field=models.CharField(blank=True, max_length=100, null=True),
),
]

Wyświetl plik

@ -12,6 +12,7 @@ class TrackFavorite(models.Model):
track = models.ForeignKey(
Track, related_name="track_favorites", on_delete=models.CASCADE
)
source = models.CharField(max_length=100, null=True, blank=True)
class Meta:
unique_together = ("track", "user")

Wyświetl plik

@ -4,6 +4,7 @@ from rest_framework import mixins, status, viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
from config import plugins
from funkwhale_api.activity import record
from funkwhale_api.common import fields, permissions
from funkwhale_api.music import utils as music_utils
@ -44,6 +45,11 @@ class TrackFavoriteViewSet(
instance = self.perform_create(serializer)
serializer = self.get_serializer(instance=instance)
headers = self.get_success_headers(serializer.data)
plugins.trigger_hook(
plugins.FAVORITE_CREATED,
track_favorite=serializer.instance,
confs=plugins.get_confs(self.request.user),
)
record.send(instance)
return Response(
serializer.data, status=status.HTTP_201_CREATED, headers=headers
@ -76,6 +82,11 @@ class TrackFavoriteViewSet(
except (AttributeError, ValueError, models.TrackFavorite.DoesNotExist):
return Response({}, status=400)
favorite.delete()
plugins.trigger_hook(
plugins.FAVORITE_DELETED,
track_favorite=favorite,
confs=plugins.get_confs(self.request.user),
)
return Response([], status=status.HTTP_204_NO_CONTENT)
@extend_schema(

Wyświetl plik

@ -1,4 +1,5 @@
from django.conf.urls import include, url
from django.conf.urls import include
from django.urls import re_path
from rest_framework import routers
from . import views
@ -23,6 +24,8 @@ music_router.register(r"tracks", views.MusicTrackViewSet, "tracks")
index_router.register(r"index", views.IndexViewSet, "index")
urlpatterns = router.urls + [
url("federation/music/", include((music_router.urls, "music"), namespace="music")),
url("federation/", include((index_router.urls, "index"), namespace="index")),
re_path(
"federation/music/", include((music_router.urls, "music"), namespace="music")
),
re_path("federation/", include((index_router.urls, "index"), namespace="index")),
]

Wyświetl plik

@ -0,0 +1,18 @@
# Generated by Django 3.2.20 on 2023-12-09 14:23
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('history', '0002_auto_20180325_1433'),
]
operations = [
migrations.AddField(
model_name='listening',
name='source',
field=models.CharField(blank=True, max_length=100, null=True),
),
]

Wyświetl plik

@ -17,6 +17,7 @@ class Listening(models.Model):
on_delete=models.CASCADE,
)
session_key = models.CharField(max_length=100, null=True, blank=True)
source = models.CharField(max_length=100, null=True, blank=True)
class Meta:
ordering = ("-creation_date",)

Wyświetl plik

@ -1,3 +1,4 @@
import pycountry
from django.core.validators import FileExtensionValidator
from django.forms import widgets
from dynamic_preferences import types
@ -170,3 +171,18 @@ class Banner(ImagePreference):
default = None
help_text = "This banner will be displayed on your pod's landing and about page. At least 600x100px recommended."
field_kwargs = {"required": False}
@global_preferences_registry.register
class Location(types.ChoicePreference):
show_in_api = True
section = instance
name = "location"
verbose_name = "Server Location"
default = ""
choices = [(country.alpha_2, country.name) for country in pycountry.countries]
help_text = (
"The country or territory in which your server is located. This is displayed in the server's Nodeinfo "
"endpoint."
)
field_kwargs = {"choices": choices, "required": False}

Wyświetl plik

@ -12,6 +12,17 @@ class SoftwareSerializer(serializers.Serializer):
return "funkwhale"
class SoftwareSerializer_v2(SoftwareSerializer):
repository = serializers.SerializerMethodField()
homepage = serializers.SerializerMethodField()
def get_repository(self, obj):
return "https://dev.funkwhale.audio/funkwhale/funkwhale"
def get_homepage(self, obj):
return "https://funkwhale.audio"
class ServicesSerializer(serializers.Serializer):
inbound = serializers.ListField(child=serializers.CharField(), default=[])
outbound = serializers.ListField(child=serializers.CharField(), default=[])
@ -31,6 +42,8 @@ class UsersUsageSerializer(serializers.Serializer):
class UsageSerializer(serializers.Serializer):
users = UsersUsageSerializer()
localPosts = serializers.IntegerField(required=False)
localComments = serializers.IntegerField(required=False)
class TotalCountSerializer(serializers.Serializer):
@ -92,19 +105,14 @@ class MetadataSerializer(serializers.Serializer):
private = serializers.SerializerMethodField()
shortDescription = serializers.SerializerMethodField()
longDescription = serializers.SerializerMethodField()
rules = serializers.SerializerMethodField()
contactEmail = serializers.SerializerMethodField()
terms = serializers.SerializerMethodField()
nodeName = serializers.SerializerMethodField()
banner = serializers.SerializerMethodField()
defaultUploadQuota = serializers.SerializerMethodField()
library = serializers.SerializerMethodField()
supportedUploadExtensions = serializers.ListField(child=serializers.CharField())
allowList = serializers.SerializerMethodField()
reportTypes = ReportTypeSerializer(source="report_types", many=True)
funkwhaleSupportMessageEnabled = serializers.SerializerMethodField()
instanceSupportMessage = serializers.SerializerMethodField()
endpoints = EndpointsSerializer()
usage = MetadataUsageSerializer(source="stats", required=False)
def get_private(self, obj) -> bool:
@ -116,15 +124,9 @@ class MetadataSerializer(serializers.Serializer):
def get_longDescription(self, obj) -> str:
return obj["preferences"].get("instance__long_description")
def get_rules(self, obj) -> str:
return obj["preferences"].get("instance__rules")
def get_contactEmail(self, obj) -> str:
return obj["preferences"].get("instance__contact_email")
def get_terms(self, obj) -> str:
return obj["preferences"].get("instance__terms")
def get_nodeName(self, obj) -> str:
return obj["preferences"].get("instance__name")
@ -137,15 +139,6 @@ class MetadataSerializer(serializers.Serializer):
def get_defaultUploadQuota(self, obj) -> int:
return obj["preferences"].get("users__upload_quota")
@extend_schema_field(NodeInfoLibrarySerializer)
def get_library(self, obj):
data = obj["stats"] or {}
data["federationEnabled"] = obj["preferences"].get("federation__enabled")
data["anonymousCanListen"] = not obj["preferences"].get(
"common__api_authentication_required"
)
return NodeInfoLibrarySerializer(data).data
@extend_schema_field(AllowListStatSerializer)
def get_allowList(self, obj):
return AllowListStatSerializer(
@ -166,6 +159,62 @@ class MetadataSerializer(serializers.Serializer):
return MetadataUsageSerializer(obj["stats"]).data
class Metadata20Serializer(MetadataSerializer):
library = serializers.SerializerMethodField()
reportTypes = ReportTypeSerializer(source="report_types", many=True)
endpoints = EndpointsSerializer()
rules = serializers.SerializerMethodField()
terms = serializers.SerializerMethodField()
def get_rules(self, obj) -> str:
return obj["preferences"].get("instance__rules")
def get_terms(self, obj) -> str:
return obj["preferences"].get("instance__terms")
@extend_schema_field(NodeInfoLibrarySerializer)
def get_library(self, obj):
data = obj["stats"] or {}
data["federationEnabled"] = obj["preferences"].get("federation__enabled")
data["anonymousCanListen"] = not obj["preferences"].get(
"common__api_authentication_required"
)
return NodeInfoLibrarySerializer(data).data
class MetadataContentLocalSerializer(serializers.Serializer):
artists = serializers.IntegerField()
releases = serializers.IntegerField()
recordings = serializers.IntegerField()
hoursOfContent = serializers.IntegerField()
class MetadataContentCategorySerializer(serializers.Serializer):
name = serializers.CharField()
count = serializers.IntegerField()
class MetadataContentSerializer(serializers.Serializer):
local = MetadataContentLocalSerializer()
topMusicCategories = MetadataContentCategorySerializer(many=True)
topPodcastCategories = MetadataContentCategorySerializer(many=True)
class Metadata21Serializer(MetadataSerializer):
languages = serializers.ListField(child=serializers.CharField())
location = serializers.CharField()
content = MetadataContentSerializer()
features = serializers.ListField(child=serializers.CharField())
codeOfConduct = serializers.SerializerMethodField()
def get_codeOfConduct(self, obj) -> str:
return (
full_url("/about/pod#rules")
if obj["preferences"].get("instance__rules")
else ""
)
class NodeInfo20Serializer(serializers.Serializer):
version = serializers.SerializerMethodField()
software = SoftwareSerializer()
@ -196,9 +245,36 @@ class NodeInfo20Serializer(serializers.Serializer):
usage = {"users": {"total": 0, "activeMonth": 0, "activeHalfyear": 0}}
return UsageSerializer(usage).data
@extend_schema_field(MetadataSerializer)
@extend_schema_field(Metadata20Serializer)
def get_metadata(self, obj):
return MetadataSerializer(obj).data
return Metadata20Serializer(obj).data
class NodeInfo21Serializer(NodeInfo20Serializer):
version = serializers.SerializerMethodField()
software = SoftwareSerializer_v2()
def get_version(self, obj) -> str:
return "2.1"
@extend_schema_field(UsageSerializer)
def get_usage(self, obj):
usage = None
if obj["preferences"]["instance__nodeinfo_stats_enabled"]:
usage = obj["stats"]
usage["localPosts"] = 0
usage["localComments"] = 0
else:
usage = {
"users": {"total": 0, "activeMonth": 0, "activeHalfyear": 0},
"localPosts": 0,
"localComments": 0,
}
return UsageSerializer(usage).data
@extend_schema_field(Metadata21Serializer)
def get_metadata(self, obj):
return Metadata21Serializer(obj).data
class SpaManifestIconSerializer(serializers.Serializer):

Wyświetl plik

@ -1,6 +1,6 @@
import datetime
from django.db.models import Sum
from django.db.models import Count, F, Sum
from django.utils import timezone
from funkwhale_api.favorites.models import TrackFavorite
@ -22,6 +22,39 @@ def get():
}
def get_content():
return {
"local": {
"artists": get_artists(),
"releases": get_albums(),
"recordings": get_tracks(),
"hoursOfContent": get_music_duration(),
},
"topMusicCategories": get_top_music_categories(),
"topPodcastCategories": get_top_podcast_categories(),
}
def get_top_music_categories():
return (
models.Track.objects.filter(artist__content_category="music")
.exclude(tagged_items__tag_id=None)
.values(name=F("tagged_items__tag__name"))
.annotate(count=Count("name"))
.order_by("-count")[:3]
)
def get_top_podcast_categories():
return (
models.Track.objects.filter(artist__content_category="podcast")
.exclude(tagged_items__tag_id=None)
.values(name=F("tagged_items__tag__name"))
.annotate(count=Count("name"))
.order_by("-count")[:3]
)
def get_users():
qs = User.objects.filter(is_active=True)
now = timezone.now()

Wyświetl plik

@ -1,4 +1,4 @@
from django.conf.urls import url
from django.urls import re_path
from funkwhale_api.common import routers
@ -8,7 +8,7 @@ admin_router = routers.OptionalSlashRouter()
admin_router.register(r"admin/settings", views.AdminSettings, "admin-settings")
urlpatterns = [
url(r"^nodeinfo/2.0/?$", views.NodeInfo.as_view(), name="nodeinfo-2.0"),
url(r"^settings/?$", views.InstanceSettings.as_view(), name="settings"),
url(r"^spa-manifest.json", views.SpaManifest.as_view(), name="spa-manifest"),
re_path(r"^nodeinfo/2.0/?$", views.NodeInfo20.as_view(), name="nodeinfo-2.0"),
re_path(r"^settings/?$", views.InstanceSettings.as_view(), name="settings"),
re_path(r"^spa-manifest.json", views.SpaManifest.as_view(), name="spa-manifest"),
] + admin_router.urls

Wyświetl plik

@ -0,0 +1,7 @@
from django.urls import re_path
from . import views
urlpatterns = [
re_path(r"^nodeinfo/2.1/?$", views.NodeInfo21.as_view(), name="nodeinfo-2.1"),
]

Wyświetl plik

@ -11,6 +11,7 @@ from dynamic_preferences.api import viewsets as preferences_viewsets
from dynamic_preferences.api.serializers import GlobalPreferenceSerializer
from dynamic_preferences.registries import global_preferences_registry
from rest_framework import generics, views
from rest_framework.renderers import JSONRenderer
from rest_framework.response import Response
from funkwhale_api import __version__ as funkwhale_version
@ -58,9 +59,11 @@ class InstanceSettings(generics.GenericAPIView):
@method_decorator(ensure_csrf_cookie, name="dispatch")
class NodeInfo(views.APIView):
class NodeInfo20(views.APIView):
permission_classes = []
authentication_classes = []
serializer_class = serializers.NodeInfo20Serializer
renderer_classes = (JSONRenderer,)
@extend_schema(
responses=serializers.NodeInfo20Serializer, operation_id="getNodeInfo20"
@ -81,6 +84,7 @@ class NodeInfo(views.APIView):
data = {
"software": {"version": funkwhale_version},
"services": {"inbound": ["atom1.0"], "outbound": ["atom1.0"]},
"preferences": pref,
"stats": cache_memoize(600, prefix="memoize:instance:stats")(stats.get)()
if pref["instance__nodeinfo_stats_enabled"]
@ -112,7 +116,65 @@ class NodeInfo(views.APIView):
data["endpoints"]["channels"] = reverse(
"federation:index:index-channels"
)
serializer = serializers.NodeInfo20Serializer(data)
serializer = self.serializer_class(data)
return Response(
serializer.data, status=200, content_type=NODEINFO_2_CONTENT_TYPE
)
class NodeInfo21(NodeInfo20):
serializer_class = serializers.NodeInfo21Serializer
@extend_schema(
responses=serializers.NodeInfo20Serializer, operation_id="getNodeInfo20"
)
def get(self, request):
pref = preferences.all()
if (
pref["moderation__allow_list_public"]
and pref["moderation__allow_list_enabled"]
):
allowed_domains = list(
Domain.objects.filter(allowed=True)
.order_by("name")
.values_list("name", flat=True)
)
else:
allowed_domains = None
data = {
"software": {"version": funkwhale_version},
"services": {"inbound": ["atom1.0"], "outbound": ["atom1.0"]},
"preferences": pref,
"stats": cache_memoize(600, prefix="memoize:instance:stats")(stats.get)()
if pref["instance__nodeinfo_stats_enabled"]
else None,
"actorId": get_service_actor().fid,
"supportedUploadExtensions": SUPPORTED_EXTENSIONS,
"allowed_domains": allowed_domains,
"languages": pref.get("moderation__languages"),
"location": pref.get("instance__location"),
"content": cache_memoize(600, prefix="memoize:instance:content")(
stats.get_content
)()
if pref["instance__nodeinfo_stats_enabled"]
else None,
"features": [
"channels",
"podcasts",
],
}
if not pref.get("common__api_authentication_required"):
data["features"].append("anonymousCanListen")
if pref.get("federation__enabled"):
data["features"].append("federation")
if pref.get("music__only_allow_musicbrainz_tagged_files"):
data["features"].append("onlyMbidTaggedContent")
serializer = self.serializer_class(data)
return Response(
serializer.data, status=200, content_type=NODEINFO_2_CONTENT_TYPE
)

Wyświetl plik

@ -1,4 +1,5 @@
from django.conf.urls import include, url
from django.conf.urls import include
from django.urls import re_path
from funkwhale_api.common import routers
@ -32,14 +33,16 @@ other_router.register(r"channels", views.ManageChannelViewSet, "channels")
other_router.register(r"tags", views.ManageTagViewSet, "tags")
urlpatterns = [
url(
re_path(
r"^federation/",
include((federation_router.urls, "federation"), namespace="federation"),
),
url(r"^library/", include((library_router.urls, "instance"), namespace="library")),
url(
re_path(
r"^library/", include((library_router.urls, "instance"), namespace="library")
),
re_path(
r"^moderation/",
include((moderation_router.urls, "moderation"), namespace="moderation"),
),
url(r"^users/", include((users_router.urls, "instance"), namespace="users")),
re_path(r"^users/", include((users_router.urls, "instance"), namespace="users")),
] + other_router.urls

Wyświetl plik

@ -1,3 +1,4 @@
import pycountry
from dynamic_preferences import types
from dynamic_preferences.registries import global_preferences_registry
from rest_framework import serializers
@ -92,3 +93,18 @@ class SignupFormCustomization(common_preferences.SerializedPreference):
required = False
default = {}
data_serializer_class = CustomFormSerializer
@global_preferences_registry.register
class Languages(common_preferences.StringListPreference):
show_in_api = True
section = moderation
name = "languages"
default = ["en"]
verbose_name = "Moderation languages"
help_text = (
"The language(s) spoken by the server moderator(s). Set this to inform users "
"what languages they should write reports and requests in."
)
choices = [(lang.alpha_3, lang.name) for lang in pycountry.languages]
field_kwargs = {"choices": choices, "required": False}

Wyświetl plik

@ -1,3 +1,4 @@
import django.dispatch
report_created = django.dispatch.Signal(providing_args=["report"])
""" Required argument: report """
report_created = django.dispatch.Signal()

Wyświetl plik

@ -32,3 +32,18 @@ class MusicCacheDuration(types.IntPreference):
"will be erased and retranscoded on the next listening."
)
field_kwargs = {"required": False}
@global_preferences_registry.register
class MbidTaggedContent(types.BooleanPreference):
show_in_api = True
section = music
name = "only_allow_musicbrainz_tagged_files"
verbose_name = "Only allow Musicbrainz tagged files"
help_text = (
"Requires uploaded files to be tagged with a MusicBrainz ID. "
"Enabling this setting has no impact on previously uploaded files. "
"You can use the CLI to clear files that don't contain an MBID or "
"or enable quality filtering to hide untagged content from API calls. "
)
default = False

Wyświetl plik

@ -104,7 +104,7 @@ class ArtistFilter(
distinct=True,
library_field="tracks__uploads__library",
)
ordering = django_filters.OrderingFilter(
ordering = common_filters.CaseInsensitiveNameOrderingFilter(
fields=(
("id", "id"),
("name", "name"),

Wyświetl plik

@ -0,0 +1,61 @@
from django.core.management.base import BaseCommand
from django.db import transaction
from funkwhale_api.music import models
class Command(BaseCommand):
help = """Deletes any tracks not tagged with a MusicBrainz ID from the database. By default, any tracks that
have been favorited by a user or added to a playlist are preserved."""
def add_arguments(self, parser):
parser.add_argument(
"--no-dry-run",
action="store_true",
dest="no_dry_run",
default=True,
help="Disable dry run mode and apply pruning for real on the database",
)
parser.add_argument(
"--include-playlist-content",
action="store_true",
dest="include_playlist_content",
default=False,
help="Allow tracks included in playlists to be pruned",
)
parser.add_argument(
"--include-favorites-content",
action="store_true",
dest="include_favorited_content",
default=False,
help="Allow favorited tracks to be pruned",
)
parser.add_argument(
"--include-listened-content",
action="store_true",
dest="include_listened_content",
default=False,
help="Allow tracks with listening history to be pruned",
)
@transaction.atomic
def handle(self, *args, **options):
tracks = models.Track.objects.filter(mbid__isnull=True)
if not options["include_favorited_content"]:
tracks = tracks.filter(track_favorites__isnull=True)
if not options["include_playlist_content"]:
tracks = tracks.filter(playlist_tracks__isnull=True)
if not options["include_listened_content"]:
tracks = tracks.filter(listenings__isnull=True)
pruned_total = tracks.count()
total = models.Track.objects.count()
if options["no_dry_run"]:
self.stdout.write(f"Deleting {pruned_total}/{total} tracks…")
tracks.delete()
else:
self.stdout.write(f"Would prune {pruned_total}/{total} tracks")

Wyświetl plik

@ -226,17 +226,18 @@ class TrackAlbumSerializer(serializers.ModelSerializer):
)
def serialize_upload(upload) -> object:
return {
"uuid": str(upload.uuid),
"listen_url": upload.listen_url,
"size": upload.size,
"duration": upload.duration,
"bitrate": upload.bitrate,
"mimetype": upload.mimetype,
"extension": upload.extension,
"is_local": federation_utils.is_local(upload.fid),
}
class TrackUploadSerializer(serializers.Serializer):
uuid = serializers.UUIDField()
listen_url = serializers.URLField()
size = serializers.IntegerField()
duration = serializers.IntegerField()
bitrate = serializers.IntegerField()
mimetype = serializers.CharField()
extension = serializers.CharField()
is_local = serializers.SerializerMethodField()
def get_is_local(self, upload) -> bool:
return federation_utils.is_local(upload.fid)
def sort_uploads_for_listen(uploads):
@ -281,11 +282,14 @@ class TrackSerializer(OptionalDescriptionMixin, serializers.Serializer):
def get_listen_url(self, obj):
return obj.listen_url
@extend_schema_field({"type": "array", "items": {"type": "object"}})
# @extend_schema_field({"type": "array", "items": {"type": "object"}})
@extend_schema_field(TrackUploadSerializer(many=True))
def get_uploads(self, obj):
uploads = getattr(obj, "playable_uploads", [])
# we put local uploads first
uploads = [serialize_upload(u) for u in sort_uploads_for_listen(uploads)]
uploads = [
TrackUploadSerializer(u).data for u in sort_uploads_for_listen(uploads)
]
uploads = sorted(uploads, key=lambda u: u["is_local"], reverse=True)
return list(uploads)

Wyświetl plik

@ -1,5 +1,4 @@
import django.dispatch
upload_import_status_updated = django.dispatch.Signal(
providing_args=["old_status", "new_status", "upload"]
)
""" Required args: old_status, new_status, upload """
upload_import_status_updated = django.dispatch.Signal()

Wyświetl plik

@ -247,6 +247,13 @@ def process_upload(upload, update_denormalization=True):
return fail_import(
upload, "invalid_metadata", detail=detail, file_metadata=metadata_dump
)
check_mbid = preferences.get("music__only_allow_musicbrainz_tagged_files")
if check_mbid and not serializer.validated_data.get("mbid"):
return fail_import(
upload,
"Only content tagged with a MusicBrainz ID is permitted on this pod.",
detail="You can tag your files with MusicBrainz Picard",
)
final_metadata = collections.ChainMap(
additional_data, serializer.validated_data, internal_config

Wyświetl plik

@ -297,8 +297,6 @@ class LibraryViewSet(
)
instance.delete()
follows = action
@extend_schema(
responses=federation_api_serializers.LibraryFollowSerializer(many=True)
)

Wyświetl plik

@ -1,4 +1,4 @@
from django.conf.urls import url
from django.urls import re_path
from funkwhale_api.common import routers
@ -7,22 +7,22 @@ from . import views
router = routers.OptionalSlashRouter()
router.register(r"search", views.SearchViewSet, "search")
urlpatterns = [
url(
re_path(
"releases/(?P<uuid>[0-9a-z-]+)/$",
views.ReleaseDetail.as_view(),
name="release-detail",
),
url(
re_path(
"artists/(?P<uuid>[0-9a-z-]+)/$",
views.ArtistDetail.as_view(),
name="artist-detail",
),
url(
re_path(
"release-groups/browse/(?P<artist_uuid>[0-9a-z-]+)/$",
views.ReleaseGroupBrowse.as_view(),
name="release-group-browse",
),
url(
re_path(
"releases/browse/(?P<release_group_uuid>[0-9a-z-]+)/$",
views.ReleaseBrowse.as_view(),
name="release-browse",

Wyświetl plik

@ -1,7 +1,8 @@
from django.conf.urls import include, url
from django.conf.urls import include
from django.urls import re_path
urlpatterns = [
url(
re_path(
r"^musicbrainz/",
include(
("funkwhale_api.musicbrainz.urls", "musicbrainz"), namespace="musicbrainz"

Wyświetl plik

@ -38,14 +38,12 @@ def validate(config):
return True
def build_radio_queryset(patch, config, radio_qs):
"""Take a troi patch and its arg, match the missing mbid and then build a radio queryset"""
logger.info("Config used for troi radio generation is " + str(config))
def build_radio_queryset(patch, radio_qs):
"""Take a troi patch, match the missing mbid and then build a radio queryset"""
start_time = time.time()
try:
recommendations = troi.core.generate_playlist(patch, config)
recommendations = patch.generate_playlist()
except ConnectTimeout:
raise ValueError(
"Timed out while connecting to ListenBrainz. No candidates could be retrieved for the radio."
@ -56,33 +54,37 @@ def build_radio_queryset(patch, config, radio_qs):
if not recommendations:
raise ValueError("No candidates found by troi")
recommended_recording_mbids = [
recommended_mbids = [
recommended_recording.mbid
for recommended_recording in recommendations.playlists[0].recordings
]
logger.info("Searching for MusicBrainz ID in Funkwhale database")
qs_mbid = music_models.Track.objects.all().filter(
mbid__in=recommended_recording_mbids
qs_recommended = (
music_models.Track.objects.all()
.filter(mbid__in=recommended_mbids)
.order_by("mbid", "pk")
.distinct("mbid")
)
mbids_found = [str(i.mbid) for i in qs_mbid]
qs_recommended_mbid = [str(i.mbid) for i in qs_recommended]
recommended_recording_mbids_not_found = [
mbid for mbid in recommended_recording_mbids if mbid not in mbids_found
recommended_mbids_not_qs = [
mbid for mbid in recommended_mbids if mbid not in qs_recommended_mbid
]
cached_mbid_match = cache.get_many(recommended_recording_mbids_not_found)
cached_match = cache.get_many(recommended_mbids_not_qs)
cached_match_mbid = [str(i) for i in cached_match.keys()]
if qs_mbid and cached_mbid_match:
if qs_recommended and cached_match_mbid:
logger.info("MusicBrainz IDs found in Funkwhale database and redis")
mbids_found = [str(i.mbid) for i in qs_mbid]
mbids_found.extend([i for i in cached_mbid_match.keys()])
elif qs_mbid and not cached_mbid_match:
qs_recommended_mbid.extend(cached_match_mbid)
mbids_found = qs_recommended_mbid
elif qs_recommended and not cached_match_mbid:
logger.info("MusicBrainz IDs found in Funkwhale database")
mbids_found = mbids_found
elif not qs_mbid and cached_mbid_match:
mbids_found = qs_recommended_mbid
elif not qs_recommended and cached_match_mbid:
logger.info("MusicBrainz IDs found in redis cache")
mbids_found = [i for i in cached_mbid_match.keys()]
mbids_found = cached_match_mbid
else:
logger.info(
"Couldn't find any matches in Funkwhale database. Trying to match all"
@ -106,23 +108,32 @@ def build_radio_queryset(patch, config, radio_qs):
+ str(end_time_resolv - start_time_resolv)
)
cached_mbid_match = cache.get_many(recommended_recording_mbids_not_found)
cached_match = cache.get_many(recommended_mbids)
if not qs_mbid and not cached_mbid_match:
if not mbids_found and not cached_match:
raise ValueError("No candidates found for troi radio")
logger.info("Radio generation with troi took " + str(end_time_resolv - start_time))
logger.info("qs_mbid is " + str(mbids_found))
mbids_found_pks = list(
music_models.Track.objects.all()
.filter(mbid__in=mbids_found)
.order_by("mbid", "pk")
.distinct("mbid")
.values_list("pk", flat=True)
)
if qs_mbid and cached_mbid_match:
mbids_found_pks_unique = [
i for i in mbids_found_pks if i not in cached_match.keys()
]
if mbids_found and cached_match:
return radio_qs.filter(
Q(mbid__in=mbids_found) | Q(pk__in=cached_mbid_match.values())
Q(pk__in=mbids_found_pks_unique) | Q(pk__in=cached_match.values())
)
if qs_mbid and not cached_mbid_match:
return radio_qs.filter(mbid__in=mbids_found)
if mbids_found and not cached_match:
return radio_qs.filter(pk__in=mbids_found_pks_unique)
if not qs_mbid and cached_mbid_match:
return radio_qs.filter(pk__in=cached_mbid_match.values())
if not mbids_found and cached_match:
return radio_qs.filter(pk__in=cached_match.values())
class TroiPatch:
@ -132,4 +143,4 @@ class TroiPatch:
def get_queryset(self, config, qs):
patch_string = config.pop("patch")
patch = patches[patch_string]
return build_radio_queryset(patch(), config, qs)
return build_radio_queryset(patch(config), qs)

Wyświetl plik

@ -6,6 +6,10 @@ from rest_framework import renderers
import funkwhale_api
class TagValue(str):
"""Use this for string values that must be rendered as tags instead of attributes in XML."""
# from https://stackoverflow.com/a/8915039
# because I want to avoid a lxml dependency just for outputting cdata properly
# in a RSS feed
@ -31,10 +35,14 @@ ET._serialize_xml = ET._serialize["xml"] = _serialize_xml
def structure_payload(data):
payload = {
# funkwhaleVersion is deprecated and will be removed in a future
# release. Use serverVersion instead.
"funkwhaleVersion": funkwhale_api.__version__,
"serverVersion": funkwhale_api.__version__,
"status": "ok",
"type": "funkwhale",
"version": "1.16.0",
"openSubsonic": "true",
}
payload.update(data)
if "detail" in payload:
@ -81,6 +89,10 @@ def dict_to_xml_tree(root_tag, d, parent=None):
el = ET.Element(key)
el.text = str(obj)
root.append(el)
elif isinstance(value, TagValue):
el = ET.Element(key)
el.text = str(value)
root.append(el)
else:
if key == "value":
root.text = str(value)

Wyświetl plik

@ -7,6 +7,8 @@ from funkwhale_api.history import models as history_models
from funkwhale_api.music import models as music_models
from funkwhale_api.music import utils as music_utils
from .renderers import TagValue
def to_subsonic_date(date):
"""
@ -50,6 +52,7 @@ def get_artist_data(artist_values):
"name": artist_values["name"],
"albumCount": artist_values["_albums_count"],
"coverArt": "ar-{}".format(artist_values["id"]),
"musicBrainzId": str(artist_values.get("mbid", "")),
}
@ -58,7 +61,7 @@ class GetArtistsSerializer(serializers.Serializer):
payload = {"ignoredArticles": "", "index": []}
queryset = queryset.with_albums_count()
queryset = queryset.order_by(functions.Lower("name"))
values = queryset.values("id", "_albums_count", "name")
values = queryset.values("id", "_albums_count", "name", "mbid")
first_letter_mapping = collections.defaultdict(list)
for artist in values:
@ -102,6 +105,23 @@ class GetArtistSerializer(serializers.Serializer):
return payload
class GetArtistInfo2Serializer(serializers.Serializer):
def to_representation(self, artist):
payload = {}
if artist.mbid:
payload["musicBrainzId"] = TagValue(artist.mbid)
if artist.attachment_cover:
payload["mediumImageUrl"] = TagValue(
artist.attachment_cover.download_url_medium_square_crop
)
payload["largeImageUrl"] = TagValue(
artist.attachment_cover.download_url_large_square_crop
)
if artist.description:
payload["biography"] = TagValue(artist.description.rendered)
return payload
def get_track_data(album, track, upload):
data = {
"id": track.pk,
@ -126,11 +146,13 @@ def get_track_data(album, track, upload):
"albumId": album.pk if album else "",
"artistId": album.artist.pk if album else track.artist.pk,
"type": "music",
"mediaType": "song",
"musicBrainzId": str(track.mbid or ""),
}
if album and album.attachment_cover_id:
data["coverArt"] = f"al-{album.id}"
if upload.bitrate:
data["bitrate"] = int(upload.bitrate / 1000)
data["bitRate"] = int(upload.bitrate / 1000)
if upload.size:
data["size"] = upload.size
if album and album.release_date:
@ -149,13 +171,17 @@ def get_album2_data(album):
"created": to_subsonic_date(album.creation_date),
"duration": album.duration,
"playCount": album.tracks.aggregate(l=Count("listenings"))["l"] or 0,
"mediaType": "album",
"musicBrainzId": str(album.mbid or ""),
}
if album.attachment_cover_id:
payload["coverArt"] = f"al-{album.id}"
if album.tagged_items:
genres = [{"name": i.tag.name} for i in album.tagged_items.all()]
# exposes only first genre since the specification uses singular noun
first_genre = album.tagged_items.first()
payload["genre"] = first_genre.tag.name if first_genre else ""
payload["genre"] = genres[0]["name"] if len(genres) > 0 else ""
# OpenSubsonic full genre list
payload["genres"] = genres
if album.release_date:
payload["year"] = album.release_date.year
try:
@ -343,7 +369,7 @@ def get_channel_episode_data(upload, channel_id):
"genre": "Podcast",
"size": upload.size if upload.size else "",
"duration": upload.duration if upload.duration else "",
"bitrate": upload.bitrate / 1000 if upload.bitrate else "",
"bitRate": upload.bitrate / 1000 if upload.bitrate else "",
"contentType": upload.mimetype or "audio/mpeg",
"suffix": upload.extension or "mp3",
"status": "completed",

Wyświetl plik

@ -180,6 +180,19 @@ class SubsonicViewSet(viewsets.GenericViewSet):
}
return response.Response(data, status=200)
@action(
detail=False,
methods=["get", "post"],
url_name="get_open_subsonic_extensions",
permission_classes=[],
url_path="getOpenSubsonicExtensions",
)
def get_open_subsonic_extensions(self, request, *args, **kwargs):
data = {
"openSubsonicExtensions": [{"name": "formPost", "versions": [1]}],
}
return response.Response(data, status=200)
@action(
detail=False,
methods=["get", "post"],
@ -255,7 +268,9 @@ class SubsonicViewSet(viewsets.GenericViewSet):
)
@find_object(music_models.Artist.objects.all(), filter_playable=True)
def get_artist_info2(self, request, *args, **kwargs):
payload = {"artist-info2": {}}
artist = kwargs.pop("obj")
data = serializers.GetArtistInfo2Serializer(artist).data
payload = {"artistInfo2": data}
return response.Response(payload, status=200)
@ -523,7 +538,7 @@ class SubsonicViewSet(viewsets.GenericViewSet):
"search_fields": ["name"],
"queryset": (
music_models.Artist.objects.with_albums_count().values(
"id", "_albums_count", "name"
"id", "_albums_count", "name", "mbid"
)
),
"serializer": lambda qs: [serializers.get_artist_data(a) for a in qs],

Wyświetl plik

@ -24,7 +24,7 @@ class TagFilter(filters.FilterSet):
def get_by_similar_tags(qs, tags):
"""
Return a queryset of obects with at least one matching tag.
Return a queryset of objects with at least one matching tag.
Annotate the queryset so you can order later by number of matches.
"""
qs = qs.filter(tagged_items__tag__name__in=tags).annotate(

Wyświetl plik

@ -36,7 +36,6 @@ def delete_non_alnum_characters(text):
def resolve_recordings_to_fw_track(recordings):
"""
Tries to match a troi recording entity to a fw track using the typesense index.
It will save the results in the match_mbid attribute of the Track table.
For test purposes : if multiple fw tracks are returned, we log the information
but only keep the best result in db to avoid duplicates.
"""

Wyświetl plik

@ -1,7 +1,7 @@
from django import forms
from django.contrib.auth.admin import UserAdmin as AuthUserAdmin
from django.contrib.auth.forms import UserChangeForm, UserCreationForm
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from funkwhale_api.common import admin

Wyświetl plik

@ -1,4 +1,4 @@
from django.conf.urls import url
from django.urls import re_path
from funkwhale_api.common import routers
@ -8,6 +8,6 @@ router = routers.OptionalSlashRouter()
router.register(r"users", views.UserViewSet, "users")
urlpatterns = [
url(r"^users/login/?$", views.login, name="login"),
url(r"^users/logout/?$", views.logout, name="logout"),
re_path(r"^users/login/?$", views.login, name="login"),
re_path(r"^users/logout/?$", views.logout, name="logout"),
] + router.urls

Wyświetl plik

@ -12,7 +12,7 @@ from django.db.models import JSONField
from django.dispatch import receiver
from django.urls import reverse
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from django_auth_ldap.backend import populate_user as ldap_populate_user
from oauth2_provider import models as oauth2_models
from oauth2_provider import validators as oauth2_validators

Wyświetl plik

@ -1,4 +1,4 @@
from django.conf.urls import url
from django.urls import re_path
from django.views.decorators.csrf import csrf_exempt
from funkwhale_api.common import routers
@ -10,7 +10,9 @@ router.register(r"apps", views.ApplicationViewSet, "apps")
router.register(r"grants", views.GrantViewSet, "grants")
urlpatterns = router.urls + [
url("^authorize/$", csrf_exempt(views.AuthorizeView.as_view()), name="authorize"),
url("^token/$", views.TokenView.as_view(), name="token"),
url("^revoke/$", views.RevokeTokenView.as_view(), name="revoke"),
re_path(
"^authorize/$", csrf_exempt(views.AuthorizeView.as_view()), name="authorize"
),
re_path("^token/$", views.TokenView.as_view(), name="token"),
re_path("^revoke/$", views.RevokeTokenView.as_view(), name="revoke"),
]

Wyświetl plik

@ -200,7 +200,7 @@ class AuthorizeView(views.APIView, oauth_views.AuthorizationView):
return self.json_payload({"non_field_errors": ["Invalid application"]}, 400)
def redirect(self, redirect_to, application):
if self.request.is_ajax():
if self.request.META.get("HTTP_X_REQUESTED_WITH") == "XMLHttpRequest":
# Web client need this to be able to redirect the user
query = urllib.parse.urlparse(redirect_to).query
code = urllib.parse.parse_qs(query)["code"][0]

Wyświetl plik

@ -1,38 +1,38 @@
from dj_rest_auth import views as rest_auth_views
from django.conf.urls import url
from django.urls import re_path
from django.views.generic import TemplateView
from . import views
urlpatterns = [
# URLs that do not require a session or valid token
url(
re_path(
r"^password/reset/$",
views.PasswordResetView.as_view(),
name="rest_password_reset",
),
url(
re_path(
r"^password/reset/confirm/$",
views.PasswordResetConfirmView.as_view(),
name="rest_password_reset_confirm",
),
# URLs that require a user to be logged in with a valid session / token.
url(
re_path(
r"^user/$", rest_auth_views.UserDetailsView.as_view(), name="rest_user_details"
),
url(
re_path(
r"^password/change/$",
views.PasswordChangeView.as_view(),
name="rest_password_change",
),
# Registration URLs
url(r"^registration/$", views.RegisterView.as_view(), name="rest_register"),
url(
re_path(r"^registration/$", views.RegisterView.as_view(), name="rest_register"),
re_path(
r"^registration/verify-email/?$",
views.VerifyEmailView.as_view(),
name="rest_verify_email",
),
url(
re_path(
r"^registration/change-password/?$",
views.PasswordChangeView.as_view(),
name="change_password",
@ -47,7 +47,7 @@ urlpatterns = [
# If you don't want to use API on that step, then just use ConfirmEmailView
# view from:
# https://github.com/pennersr/django-allauth/blob/a62a370681/allauth/account/views.py#L291
url(
re_path(
r"^registration/account-confirm-email/(?P<key>\w+)/?$",
TemplateView.as_view(),
name="account_confirm_email",

Wyświetl plik

@ -340,4 +340,8 @@ class UserChangeEmailSerializer(serializers.Serializer):
email=request.user.email,
defaults={"verified": False, "primary": True},
)
current.change(request, self.validated_data["email"], confirm=True)
if request.user.email != self.validated_data["email"]:
current.email = self.validated_data["email"]
current.verified = False
current.save()
current.send_confirmation()

Wyświetl plik

@ -1,6 +1,7 @@
import json
from allauth.account.adapter import get_adapter
from allauth.account.utils import send_email_confirmation
from dj_rest_auth import views as rest_auth_views
from dj_rest_auth.registration import views as registration_views
from django import http
@ -11,7 +12,7 @@ from rest_framework import mixins, viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
from funkwhale_api.common import authentication, preferences, throttling
from funkwhale_api.common import preferences, throttling
from . import models, serializers, tasks
@ -37,7 +38,7 @@ class RegisterView(registration_views.RegisterView):
user = super().perform_create(serializer)
if not user.is_active:
# manual approval, we need to send the confirmation e-mail by hand
authentication.send_email_confirmation(self.request, user)
send_email_confirmation(self.request, user)
if user.invitation:
user.invitation.set_invited_user(user)

4026
api/poetry.lock wygenerowano

Plik diff jest za duży Load Diff

Wyświetl plik

@ -1,6 +1,6 @@
[tool.poetry]
name = "funkwhale-api"
version = "1.3.3"
version = "1.4.0"
description = "Funkwhale API"
authors = ["Funkwhale Collective"]
@ -25,102 +25,104 @@ exclude = ["tests"]
funkwhale-manage = 'funkwhale_api.main:main'
[tool.poetry.dependencies]
python = "^3.8"
python = "^3.8,<3.13"
# Django
dj-rest-auth = { extras = ["with_social"], version = "2.2.8" }
django = "==3.2.20"
django-allauth = "==0.42.0"
dj-rest-auth = "5.0.2"
django = "4.2.9"
django-allauth = "0.55.2"
django-cache-memoize = "0.1.10"
django-cacheops = "==6.1"
django-cleanup = "==6.0.0"
django-cors-headers = "==3.13.0"
django-cacheops = "==7.0.2"
django-cleanup = "==8.1.0"
django-cors-headers = "==4.3.1"
django-dynamic-preferences = "==1.14.0"
django-environ = "==0.10.0"
django-filter = "==22.1"
django-filter = "==23.5"
django-oauth-toolkit = "2.2.0"
django-redis = "==5.2.0"
django-storages = "==1.13.2"
django-versatileimagefield = "==2.2"
django-versatileimagefield = "==3.1"
djangorestframework = "==3.14.0"
drf-spectacular = "==0.26.1"
drf-spectacular = "==0.26.5"
markdown = "==3.4.4"
persisting-theory = "==1.0"
psycopg2 = "==2.9.7"
redis = "==4.5.5"
psycopg2 = "==2.9.9"
redis = "==5.0.1"
# Django LDAP
django-auth-ldap = "==4.1.0"
python-ldap = "==3.4.3"
python-ldap = "==3.4.4"
# Channels
channels = { extras = ["daphne"], version = "==4.0.0" }
channels-redis = "==4.1.0"
# Celery
kombu = "==5.2.4"
celery = "==5.2.7"
kombu = "5.3.4"
celery = "5.3.6"
# Deployment
gunicorn = "==20.1.0"
gunicorn = "==21.2.0"
uvicorn = { version = "==0.20.0", extras = ["standard"] }
# Libs
aiohttp = "==3.8.5"
aiohttp = "3.9.1"
arrow = "==1.2.3"
backports-zoneinfo = { version = "==0.2.1", python = "<3.9" }
bleach = "==5.0.1"
bleach = "==6.1.0"
boto3 = "==1.26.161"
click = "==8.1.7"
cryptography = "==38.0.4"
cryptography = "==41.0.7"
feedparser = "==6.0.10"
liblistenbrainz = "==0.5.5"
musicbrainzngs = "==0.7.1"
mutagen = "==1.46.0"
pillow = "==9.3.0"
pillow = "==10.2.0"
pydub = "==0.25.1"
pyld = "==2.0.3"
python-magic = "==0.4.27"
requests = "==2.28.2"
requests = "==2.31.0"
requests-http-message-signatures = "==0.3.1"
sentry-sdk = "==1.19.1"
watchdog = "==2.2.1"
troi = { git = "https://github.com/metabrainz/troi-recommendation-playground.git", branch = "main"}
lb-matching-tools = { git = "https://github.com/metabrainz/listenbrainz-matching-tools.git", branch = "main"}
unidecode = "==1.3.6"
watchdog = "==4.0.0"
troi = "==2024.1.26.0"
lb-matching-tools = "==2024.1.25.0rc1"
unidecode = "==1.3.7"
pycountry = "23.12.11"
# Typesense
typesense = { version = "==0.15.1", optional = true }
# Dependencies pinning
ipython = "==7.34.0"
ipython = "==8.12.3"
pluralizer = "==1.2.0"
service-identity = "==21.1.0"
service-identity = "==24.1.0"
unicode-slugify = "==0.1.5"
[tool.poetry.group.dev.dependencies]
aioresponses = "==0.7.4"
aioresponses = "==0.7.6"
asynctest = "==0.13.0"
black = "==23.3.0"
coverage = { version = "==6.5.0", extras = ["toml"] }
black = "==24.1.1"
coverage = { version = "==7.4.1", extras = ["toml"] }
debugpy = "==1.6.7.post1"
django-coverage-plugin = "==3.0.0"
django-debug-toolbar = "==3.8.1"
django-debug-toolbar = "==4.2.0"
factory-boy = "==3.2.1"
faker = "==15.3.4"
faker = "==23.2.1"
flake8 = "==3.9.2"
ipdb = "==0.13.13"
prompt-toolkit = "==3.0.39"
pytest = "==7.2.2"
pytest = "==8.0.0"
pytest-asyncio = "==0.21.0"
prompt-toolkit = "==3.0.41"
pytest-cov = "==4.0.0"
pytest-django = "==4.5.2"
pytest-env = "==0.8.1"
pytest-env = "==1.1.3"
pytest-mock = "==3.10.0"
pytest-randomly = "==3.12.0"
pytest-sugar = "==0.9.7"
pytest-sugar = "==1.0.0"
requests-mock = "==1.10.0"
pylint = "==2.17.2"
pylint-django = "==2.5.3"
pylint = "==3.0.3"
pylint-django = "==2.5.5"
django-extensions = "==3.2.3"
[tool.poetry.extras]

Wyświetl plik

@ -108,7 +108,7 @@ def test_get_default_head_tags(preferences, settings):
{
"tag": "meta",
"property": "og:image",
"content": settings.FUNKWHALE_URL + "/front/favicon.png",
"content": settings.FUNKWHALE_URL + "/android-chrome-512x512.png",
},
{"tag": "meta", "property": "og:url", "content": settings.FUNKWHALE_URL + "/"},
]

Wyświetl plik

@ -17,7 +17,7 @@ def test_get_ident_anonymous(api_request):
def test_get_ident_authenticated(api_request, factories):
user = factories["users.User"]()
request = api_request.get("/")
expected = {"id": user.pk, "type": "authenticated"}
expected = {"id": f"{user.pk}", "type": "authenticated"}
assert throttling.get_ident(user, request) == expected
@ -26,7 +26,7 @@ def test_get_ident_authenticated(api_request, factories):
[
(
"create",
{"id": 42, "type": "authenticated"},
{"id": "42", "type": "authenticated"},
"throttling:create:authenticated:42",
),
(
@ -269,6 +269,7 @@ def test_throttle_calls_attach_info(method, mocker):
def test_allow_request(api_request, settings, mocker):
settings.THROTTLING_ENABLED = True
settings.THROTTLING_RATES = {"test": {"rate": "2/s"}}
ip = "92.92.92.92"
request = api_request.get("/", HTTP_X_FORWARDED_FOR=ip)

Wyświetl plik

@ -160,7 +160,7 @@ def test_cannot_approve_reject_without_perm(
def test_rate_limit(logged_in_api_client, now_time, settings, mocker):
expected_ident = {"type": "authenticated", "id": logged_in_api_client.user.pk}
expected_ident = {"type": "authenticated", "id": f"{logged_in_api_client.user.pk}"}
expected = {
"ident": expected_ident,

Wyświetl plik

@ -0,0 +1,333 @@
import datetime
import logging
import liblistenbrainz
import pytest
from django.urls import reverse
from django.utils import timezone
from config import plugins
from funkwhale_api.contrib.listenbrainz import funkwhale_ready
from funkwhale_api.favorites import models as favorites_models
from funkwhale_api.history import models as history_models
def test_listenbrainz_submit_listen(logged_in_client, mocker, factories):
config = plugins.get_plugin_config(
name="listenbrainz",
description="A plugin that allows you to submit or sync your listens and favorites to ListenBrainz.",
conf=[],
source=False,
)
handler = mocker.Mock()
plugins.register_hook(plugins.LISTENING_CREATED, config)(handler)
plugins.set_conf(
"listenbrainz",
{
"sync_listenings": True,
"sync_favorites": True,
"submit_favorites": True,
"sync_favorites": True,
"user_token": "blablabla",
},
user=logged_in_client.user,
)
plugins.enable_conf("listenbrainz", True, logged_in_client.user)
track = factories["music.Track"]()
url = reverse("api:v1:history:listenings-list")
logged_in_client.post(url, {"track": track.pk})
logged_in_client.get(url)
listening = history_models.Listening.objects.get(user=logged_in_client.user)
handler.assert_called_once_with(listening=listening, conf=None)
def test_sync_listenings_from_listenbrainz(factories, mocker, caplog):
logger = logging.getLogger("plugins")
caplog.set_level(logging.INFO)
logger.addHandler(caplog.handler)
user = factories["users.User"]()
factories["music.Track"](mbid="f89db7f8-4a1f-4228-a0a1-e7ba028b7476")
track = factories["music.Track"](mbid="54c60860-f43d-484e-b691-7ab7ec8de559")
factories["history.Listening"](
creation_date=datetime.datetime.fromtimestamp(1871, timezone.utc), track=track
)
conf = {
"user_name": user.username,
"user_token": "user_tolkien",
"sync_listenings": True,
}
listens = {
"payload": {
"count": 25,
"user_id": "-- the MusicBrainz ID of the user --",
"listens": [
liblistenbrainz.Listen(
track_name="test",
artist_name="artist_test",
recording_mbid="f89db7f8-4a1f-4228-a0a1-e7ba028b7476",
additional_info={"submission_client": "not funkwhale"},
listened_at=-3124224000,
),
liblistenbrainz.Listen(
track_name="test2",
artist_name="artist_test2",
recording_mbid="54c60860-f43d-484e-b691-7ab7ec8de559",
additional_info={
"submission_client": "Funkwhale ListenBrainz plugin"
},
listened_at=1871,
),
liblistenbrainz.Listen(
track_name="test3",
artist_name="artist_test3",
listened_at=0,
),
],
}
}
no_more_listen = {
"payload": {
"count": 25,
"user_id": "Bilbo",
"listens": [],
}
}
mocker.patch.object(
funkwhale_ready.tasks.liblistenbrainz.ListenBrainz,
"get_listens",
side_effect=[listens, no_more_listen],
)
funkwhale_ready.sync_listenings_from_listenbrainz(user, conf)
assert history_models.Listening.objects.filter(
track__mbid="f89db7f8-4a1f-4228-a0a1-e7ba028b7476"
).exists()
assert "Listen with ts 1871 skipped because already in db" in caplog.text
assert "Received listening that doesn't have a mbid. Skipping..." in caplog.text
def test_sync_favorites_from_listenbrainz(factories, mocker, caplog):
logger = logging.getLogger("plugins")
caplog.set_level(logging.INFO)
logger.addHandler(caplog.handler)
user = factories["users.User"]()
# track lb fav
factories["music.Track"](mbid="195565db-65f9-4d0d-b347-5f0c85509528")
# random track
factories["music.Track"]()
# track lb neutral
track = factories["music.Track"](mbid="c5af5351-dbbf-4481-b52e-a480b6c57986")
favorite = factories["favorites.TrackFavorite"](track=track, user=user)
# last_sync
track_last_sync = factories["music.Track"](
mbid="c878ef2f-c08d-4a81-a047-f2a9f978cec7"
)
factories["favorites.TrackFavorite"](track=track_last_sync, source="Listenbrainz")
conf = {
"user_name": user.username,
"user_token": "user_tolkien",
"sync_favorites": True,
}
feedbacks = {
"count": 5,
"feedback": [
{
"created": 1701116226,
"recording_mbid": "195565db-65f9-4d0d-b347-5f0c85509528",
"score": 1,
"user_id": user.username,
},
{
"created": 1701116214,
"recording_mbid": "c5af5351-dbbf-4481-b52e-a480b6c57986",
"score": 0,
"user_id": user.username,
},
{
# last sync
"created": 1690775094,
"recording_mbid": "c878ef2f-c08d-4a81-a047-f2a9f978cec7",
"score": -1,
"user_id": user.username,
},
{
"created": 1690775093,
"recording_mbid": "1fd02cf2-7247-4715-8862-c378ec1965d2",
"score": 1,
"user_id": user.username,
},
],
"offset": 0,
"total_count": 4,
}
empty_feedback = {"count": 0, "feedback": [], "offset": 0, "total_count": 0}
mocker.patch.object(
funkwhale_ready.tasks.liblistenbrainz.ListenBrainz,
"get_user_feedback",
side_effect=[feedbacks, empty_feedback],
)
funkwhale_ready.sync_favorites_from_listenbrainz(user, conf)
assert favorites_models.TrackFavorite.objects.filter(
track__mbid="195565db-65f9-4d0d-b347-5f0c85509528"
).exists()
with pytest.raises(favorites_models.TrackFavorite.DoesNotExist):
favorite.refresh_from_db()
def test_sync_favorites_from_listenbrainz_since(factories, mocker, caplog):
logger = logging.getLogger("plugins")
caplog.set_level(logging.INFO)
logger.addHandler(caplog.handler)
user = factories["users.User"]()
# track lb fav
factories["music.Track"](mbid="195565db-65f9-4d0d-b347-5f0c85509528")
# track lb neutral
track = factories["music.Track"](mbid="c5af5351-dbbf-4481-b52e-a480b6c57986")
favorite = factories["favorites.TrackFavorite"](track=track, user=user)
# track should be not synced
factories["music.Track"](mbid="1fd02cf2-7247-4715-8862-c378ec196000")
# last_sync
track_last_sync = factories["music.Track"](
mbid="c878ef2f-c08d-4a81-a047-f2a9f978cec7"
)
factories["favorites.TrackFavorite"](
track=track_last_sync,
user=user,
source="Listenbrainz",
creation_date=datetime.datetime.fromtimestamp(1690775094),
)
conf = {
"user_name": user.username,
"user_token": "user_tolkien",
"sync_favorites": True,
}
feedbacks = {
"count": 5,
"feedback": [
{
"created": 1701116226,
"recording_mbid": "195565db-65f9-4d0d-b347-5f0c85509528",
"score": 1,
"user_id": user.username,
},
{
"created": 1701116214,
"recording_mbid": "c5af5351-dbbf-4481-b52e-a480b6c57986",
"score": 0,
"user_id": user.username,
},
{
# last sync
"created": 1690775094,
"recording_mbid": "c878ef2f-c08d-4a81-a047-f2a9f978cec7",
"score": -1,
"user_id": user.username,
},
{
"created": 1690775093,
"recording_mbid": "1fd02cf2-7247-4715-8862-c378ec1965d2",
"score": 1,
"user_id": user.username,
},
],
"offset": 0,
"total_count": 4,
}
second_feedback = {
"count": 0,
"feedback": [
{
"created": 0,
"recording_mbid": "1fd02cf2-7247-4715-8862-c378ec196000",
"score": 1,
"user_id": user.username,
},
],
"offset": 0,
"total_count": 0,
}
mocker.patch.object(
funkwhale_ready.tasks.liblistenbrainz.ListenBrainz,
"get_user_feedback",
side_effect=[feedbacks, second_feedback],
)
funkwhale_ready.sync_favorites_from_listenbrainz(user, conf)
assert favorites_models.TrackFavorite.objects.filter(
track__mbid="195565db-65f9-4d0d-b347-5f0c85509528"
).exists()
assert not favorites_models.TrackFavorite.objects.filter(
track__mbid="1fd02cf2-7247-4715-8862-c378ec196000"
).exists()
with pytest.raises(favorites_models.TrackFavorite.DoesNotExist):
favorite.refresh_from_db()
def test_submit_favorites_to_listenbrainz(factories, mocker, caplog):
logger = logging.getLogger("plugins")
caplog.set_level(logging.INFO)
logger.addHandler(caplog.handler)
user = factories["users.User"]()
factories["music.Track"](mbid="195565db-65f9-4d0d-b347-5f0c85509528")
factories["music.Track"](mbid="54c60860-f43d-484e-b691-7ab7ec8de559")
track = factories["music.Track"](mbid="c5af5351-dbbf-4481-b52e-a480b6c57986")
favorite = factories["favorites.TrackFavorite"](track=track)
conf = {
"user_name": user.username,
"user_token": "user_tolkien",
"submit_favorites": True,
}
patch = mocker.patch.object(
funkwhale_ready.tasks.liblistenbrainz.ListenBrainz,
"submit_user_feedback",
return_value="Success",
)
funkwhale_ready.submit_favorite_creation(favorite, conf)
patch.assert_called_once_with(1, track.mbid)
def test_submit_favorites_deletion(factories, mocker, caplog):
logger = logging.getLogger("plugins")
caplog.set_level(logging.INFO)
logger.addHandler(caplog.handler)
user = factories["users.User"]()
factories["music.Track"](mbid="195565db-65f9-4d0d-b347-5f0c85509528")
factories["music.Track"](mbid="54c60860-f43d-484e-b691-7ab7ec8de559")
track = factories["music.Track"](mbid="c5af5351-dbbf-4481-b52e-a480b6c57986")
favorite = factories["favorites.TrackFavorite"](track=track)
conf = {
"user_name": user.username,
"user_token": "user_tolkien",
"submit_favorites": True,
}
patch = mocker.patch.object(
funkwhale_ready.tasks.liblistenbrainz.ListenBrainz,
"submit_user_feedback",
return_value="Success",
)
funkwhale_ready.submit_favorite_deletion(favorite, conf)
patch.assert_called_once_with(0, track.mbid)

Wyświetl plik

@ -6,7 +6,7 @@ from funkwhale_api import __version__ as api_version
from funkwhale_api.music.utils import SUPPORTED_EXTENSIONS
def test_nodeinfo_default(api_client):
def test_nodeinfo_20(api_client):
url = reverse("api:v1:instance:nodeinfo-2.0")
response = api_client.get(url)
@ -14,7 +14,7 @@ def test_nodeinfo_default(api_client):
"version": "2.0",
"software": OrderedDict([("name", "funkwhale"), ("version", api_version)]),
"protocols": ["activitypub"],
"services": OrderedDict([("inbound", []), ("outbound", [])]),
"services": OrderedDict([("inbound", ["atom1.0"]), ("outbound", ["atom1.0"])]),
"openRegistrations": False,
"usage": {
"users": OrderedDict(
@ -89,3 +89,74 @@ def test_nodeinfo_default(api_client):
}
assert response.data == expected
def test_nodeinfo_21(api_client):
url = reverse("api:v2:instance:nodeinfo-2.1")
response = api_client.get(url)
expected = {
"version": "2.1",
"software": OrderedDict(
[
("name", "funkwhale"),
("version", api_version),
("repository", "https://dev.funkwhale.audio/funkwhale/funkwhale"),
("homepage", "https://funkwhale.audio"),
]
),
"protocols": ["activitypub"],
"services": OrderedDict([("inbound", ["atom1.0"]), ("outbound", ["atom1.0"])]),
"openRegistrations": False,
"usage": {
"users": OrderedDict(
[("total", 0), ("activeHalfyear", 0), ("activeMonth", 0)]
),
"localPosts": 0,
"localComments": 0,
},
"metadata": {
"actorId": "https://test.federation/federation/actors/service",
"private": False,
"shortDescription": "",
"longDescription": "",
"contactEmail": "",
"nodeName": "",
"banner": None,
"defaultUploadQuota": 1000,
"supportedUploadExtensions": SUPPORTED_EXTENSIONS,
"allowList": {"enabled": False, "domains": None},
"funkwhaleSupportMessageEnabled": True,
"instanceSupportMessage": "",
"usage": OrderedDict(
[
("favorites", OrderedDict([("tracks", {"total": 0})])),
("listenings", OrderedDict([("total", 0)])),
("downloads", OrderedDict([("total", 0)])),
]
),
"location": "",
"languages": ["en"],
"features": ["channels", "podcasts", "federation"],
"content": OrderedDict(
[
(
"local",
OrderedDict(
[
("artists", 0),
("releases", 0),
("recordings", 0),
("hoursOfContent", 0),
]
),
),
("topMusicCategories", []),
("topPodcastCategories", []),
]
),
"codeOfConduct": "",
},
}
assert response.data == expected

Wyświetl plik

@ -7,6 +7,7 @@ from funkwhale_api.music.management.commands import (
check_inplace_files,
fix_uploads,
prune_library,
prune_non_mbid_content,
)
DATA_DIR = os.path.dirname(os.path.abspath(__file__))
@ -204,3 +205,45 @@ def test_check_inplace_files_no_dry_run(factories, tmpfile):
for u in not_prunable:
u.refresh_from_db()
def test_prune_non_mbid_content(factories):
prunable = factories["music.Track"](mbid=None)
track = factories["music.Track"](mbid=None)
factories["playlists.PlaylistTrack"](track=track)
not_prunable = [factories["music.Track"](), track]
c = prune_non_mbid_content.Command()
options = {
"include_playlist_content": False,
"include_listened_content": False,
"include_favorited_content": True,
"no_dry_run": True,
}
c.handle(**options)
with pytest.raises(prunable.DoesNotExist):
prunable.refresh_from_db()
for t in not_prunable:
t.refresh_from_db()
track = factories["music.Track"](mbid=None)
factories["playlists.PlaylistTrack"](track=track)
prunable = [factories["music.Track"](mbid=None), track]
not_prunable = [factories["music.Track"]()]
options = {
"include_playlist_content": True,
"include_listened_content": False,
"include_favorited_content": False,
"no_dry_run": True,
}
c.handle(**options)
for t in prunable:
with pytest.raises(t.DoesNotExist):
t.refresh_from_db()
for t in not_prunable:
t.refresh_from_db()

Wyświetl plik

@ -3,9 +3,32 @@ import pytest
from funkwhale_api.music import filters, models
def test_artist_filter_ordering(factories, mocker):
# Lista de prueba
artist1 = factories["music.Artist"](name="Anita Muller")
artist2 = factories["music.Artist"](name="Jane Smith")
artist3 = factories["music.Artist"](name="Adam Johnson")
artist4 = factories["music.Artist"](name="anita iux")
qs = models.Artist.objects.all()
cf = factories["moderation.UserFilter"](for_artist=True)
# Request con ordenamiento
filterset = filters.ArtistFilter(
{"ordering": "name"}, request=mocker.Mock(user=cf.user), queryset=qs
)
expected_order = [artist3.name, artist4.name, artist1.name, artist2.name]
actual_order = list(filterset.qs.values_list("name", flat=True))
assert actual_order == expected_order
def test_album_filter_hidden(factories, mocker, queryset_equal_list):
factories["music.Album"]()
cf = factories["moderation.UserFilter"](for_artist=True)
hidden_album = factories["music.Album"](artist=cf.target_artist)
qs = models.Album.objects.all()

Wyświetl plik

@ -198,8 +198,8 @@ def test_can_get_pictures(name):
cover_data = data.get_picture("cover_front", "other")
assert cover_data["mimetype"].startswith("image/")
assert len(cover_data["content"]) > 0
assert type(cover_data["content"]) == bytes
assert type(cover_data["description"]) == str
assert type(cover_data["content"]) is bytes
assert type(cover_data["description"]) is str
@pytest.mark.parametrize(

Wyświetl plik

@ -245,7 +245,7 @@ def test_track_serializer(factories, to_api_date):
"title": track.title,
"position": track.position,
"disc_number": track.disc_number,
"uploads": [serializers.serialize_upload(upload)],
"uploads": [serializers.TrackUploadSerializer(upload).data],
"creation_date": to_api_date(track.creation_date),
"listen_url": track.listen_url,
"license": upload.track.license.code,
@ -373,7 +373,7 @@ def test_manage_upload_action_publish(factories, mocker):
m.assert_any_call(tasks.process_upload.delay, upload_id=draft.pk)
def test_serialize_upload(factories):
def test_track_upload_serializer(factories):
upload = factories["music.Upload"]()
expected = {
@ -387,7 +387,7 @@ def test_serialize_upload(factories):
"is_local": False,
}
data = serializers.serialize_upload(upload)
data = serializers.TrackUploadSerializer(upload).data
assert data == expected

Wyświetl plik

@ -1400,3 +1400,53 @@ def test_fs_import(factories, cache, mocker, settings):
}
assert cache.get("fs-import:status") == "finished"
assert "Pruning dangling tracks" in cache.get("fs-import:logs")[-1]
def test_upload_checks_mbid_tag(temp_signal, factories, mocker, preferences):
preferences["music__only_allow_musicbrainz_tagged_files"] = True
mocker.patch("funkwhale_api.federation.routes.outbox.dispatch")
mocker.patch("funkwhale_api.music.tasks.populate_album_cover")
mocker.patch("funkwhale_api.music.metadata.Metadata.get_picture")
track = factories["music.Track"](album__attachment_cover=None, mbid=None)
path = os.path.join(DATA_DIR, "with_cover.opus")
upload = factories["music.Upload"](
track=None,
audio_file__from_path=path,
import_metadata={"funkwhale": {"track": {"uuid": str(track.uuid)}}},
)
mocker.patch("funkwhale_api.music.models.TrackActor.create_entries")
with temp_signal(signals.upload_import_status_updated):
tasks.process_upload(upload_id=upload.pk)
upload.refresh_from_db()
assert upload.import_status == "errored"
assert upload.import_details == {
"error_code": "Only content tagged with a MusicBrainz ID is permitted on this pod.",
"detail": "You can tag your files with MusicBrainz Picard",
}
def test_upload_checks_mbid_tag_pass(temp_signal, factories, mocker, preferences):
preferences["music__only_allow_musicbrainz_tagged_files"] = True
mocker.patch("funkwhale_api.federation.routes.outbox.dispatch")
mocker.patch("funkwhale_api.music.tasks.populate_album_cover")
mocker.patch("funkwhale_api.music.metadata.Metadata.get_picture")
track = factories["music.Track"](album__attachment_cover=None, mbid=None)
path = os.path.join(DATA_DIR, "test.mp3")
upload = factories["music.Upload"](
track=None,
audio_file__from_path=path,
import_metadata={"funkwhale": {"track": {"uuid": str(track.uuid)}}},
)
mocker.patch("funkwhale_api.music.models.TrackActor.create_entries")
with temp_signal(signals.upload_import_status_updated):
tasks.process_upload(upload_id=upload.pk)
upload.refresh_from_db()
assert upload.import_status == "finished"

Wyświetl plik

@ -131,3 +131,12 @@ def test_transcode_file(name, expected):
result = {k: round(v) for k, v in utils.get_audio_file_data(f).items()}
assert result == expected
def test_custom_s3_domain(factories, settings):
"""See #2220"""
settings.AWS_S3_CUSTOM_DOMAIN = "my.custom.domain.tld"
settings.DEFAULT_FILE_STORAGE = "funkwhale_api.common.storage.ASCIIS3Boto3Storage"
f = factories["music.Upload"].build(audio_file__filename="test.mp3")
assert f.audio_file.url.startswith("https://")

Wyświetl plik

@ -24,7 +24,7 @@ def test_can_build_radio_queryset_with_fw_db(factories, mocker):
mocker.patch("funkwhale_api.typesense.utils.resolve_recordings_to_fw_track")
radio_qs = lb_recommendations.build_radio_queryset(
custom_factories.DummyPatch(), {"min_recordings": 1}, qs
custom_factories.DummyPatch({"min_recordings": 1}), qs
)
recommended_recording_mbids = [
"87dfa566-21c3-45ed-bc42-1d345b8563fa",
@ -46,7 +46,7 @@ def test_build_radio_queryset_without_fw_db(mocker):
with pytest.raises(ValueError):
lb_recommendations.build_radio_queryset(
custom_factories.DummyPatch(), {"min_recordings": 1}, qs
custom_factories.DummyPatch({"min_recordings": 1}), qs
)
assert resolve_recordings_to_fw_track.called_once_with(
@ -67,7 +67,7 @@ def test_build_radio_queryset_with_redis_and_fw_db(factories, mocker):
assert list(
lb_recommendations.build_radio_queryset(
custom_factories.DummyPatch(), {"min_recordings": 1}, qs
custom_factories.DummyPatch({"min_recordings": 1}), qs
)
) == list(Track.objects.all().filter(pk__in=[1, 2]))
@ -84,14 +84,14 @@ def test_build_radio_queryset_with_redis_and_without_fw_db(factories, mocker):
assert list(
lb_recommendations.build_radio_queryset(
custom_factories.DummyPatch(), {"min_recordings": 1}, qs
custom_factories.DummyPatch({"min_recordings": 1}), qs
)
) == list(Track.objects.all().filter(pk=1))
def test_build_radio_queryset_catch_troi_ConnectTimeout(mocker):
mocker.patch.object(
troi.core,
troi.core.Patch,
"generate_playlist",
side_effect=ConnectTimeout,
)
@ -99,18 +99,18 @@ def test_build_radio_queryset_catch_troi_ConnectTimeout(mocker):
with pytest.raises(ValueError):
lb_recommendations.build_radio_queryset(
custom_factories.DummyPatch(), {"min_recordings": 1}, qs
custom_factories.DummyPatch({"min_recordings": 1}), qs
)
def test_build_radio_queryset_catch_troi_no_candidates(mocker):
mocker.patch.object(
troi.core,
troi.core.Patch,
"generate_playlist",
)
qs = Track.objects.all()
with pytest.raises(ValueError):
lb_recommendations.build_radio_queryset(
custom_factories.DummyPatch(), {"min_recordings": 1}, qs
custom_factories.DummyPatch({"min_recordings": 1}), qs
)

Wyświetl plik

@ -17,6 +17,8 @@ from funkwhale_api.subsonic import renderers
"version": "1.16.0",
"type": "funkwhale",
"funkwhaleVersion": funkwhale_api.__version__,
"serverVersion": funkwhale_api.__version__,
"openSubsonic": "true",
"hello": "world",
},
),
@ -30,6 +32,8 @@ from funkwhale_api.subsonic import renderers
"version": "1.16.0",
"type": "funkwhale",
"funkwhaleVersion": funkwhale_api.__version__,
"serverVersion": funkwhale_api.__version__,
"openSubsonic": "true",
"hello": "world",
"error": {"code": 10, "message": "something went wrong"},
},
@ -41,6 +45,8 @@ from funkwhale_api.subsonic import renderers
"version": "1.16.0",
"type": "funkwhale",
"funkwhaleVersion": funkwhale_api.__version__,
"serverVersion": funkwhale_api.__version__,
"openSubsonic": "true",
"hello": "world",
"error": {"code": 0, "message": "something went wrong"},
},
@ -59,6 +65,8 @@ def test_json_renderer():
"version": "1.16.0",
"type": "funkwhale",
"funkwhaleVersion": funkwhale_api.__version__,
"serverVersion": funkwhale_api.__version__,
"openSubsonic": "true",
"hello": "world",
}
}
@ -71,9 +79,10 @@ def test_xml_renderer_dict_to_xml():
"hello": "world",
"item": [{"this": 1, "value": "text"}, {"some": "node"}],
"list": [1, 2],
"some-tag": renderers.TagValue("foo"),
}
expected = """<?xml version="1.0" encoding="UTF-8"?>
<key hello="world"><item this="1">text</item><item some="node" /><list>1</list><list>2</list></key>"""
<key hello="world"><item this="1">text</item><item some="node" /><list>1</list><list>2</list><some-tag>foo</some-tag></key>""" # noqa
result = renderers.dict_to_xml_tree("key", payload)
exp = ET.fromstring(expected)
assert ET.tostring(result) == ET.tostring(exp)
@ -81,8 +90,9 @@ def test_xml_renderer_dict_to_xml():
def test_xml_renderer():
payload = {"hello": "world"}
expected = '<?xml version="1.0" encoding="UTF-8"?>\n<subsonic-response funkwhaleVersion="{}" hello="world" status="ok" type="funkwhale" version="1.16.0" xmlns="http://subsonic.org/restapi" />' # noqa
expected = expected.format(funkwhale_api.__version__).encode()
expected = '<?xml version="1.0" encoding="UTF-8"?>\n<subsonic-response funkwhaleVersion="{}" hello="world" openSubsonic="true" serverVersion="{}" status="ok" type="funkwhale" version="1.16.0" xmlns="http://subsonic.org/restapi" />' # noqa
version = funkwhale_api.__version__
expected = expected.format(version, version).encode()
renderer = renderers.SubsonicXMLRenderer()
rendered = renderer.render(payload)

Wyświetl plik

@ -4,7 +4,7 @@ import pytest
from django.db.models.aggregates import Count
from funkwhale_api.music import models as music_models
from funkwhale_api.subsonic import serializers
from funkwhale_api.subsonic import renderers, serializers
@pytest.mark.parametrize(
@ -90,12 +90,14 @@ def test_get_artists_serializer(factories):
"name": artist1.name,
"albumCount": 3,
"coverArt": f"ar-{artist1.id}",
"musicBrainzId": artist1.mbid,
},
{
"id": artist2.pk,
"name": artist2.name,
"albumCount": 2,
"coverArt": f"ar-{artist2.id}",
"musicBrainzId": artist2.mbid,
},
],
},
@ -107,6 +109,7 @@ def test_get_artists_serializer(factories):
"name": artist3.name,
"albumCount": 0,
"coverArt": f"ar-{artist3.id}",
"musicBrainzId": artist3.mbid,
}
],
},
@ -147,6 +150,24 @@ def test_get_artist_serializer(factories):
assert serializers.GetArtistSerializer(artist).data == expected
def test_get_artist_info_2_serializer(factories):
content = factories["common.Content"]()
artist = factories["music.Artist"](with_cover=True, description=content)
expected = {
"musicBrainzId": artist.mbid,
"mediumImageUrl": renderers.TagValue(
artist.attachment_cover.download_url_medium_square_crop
),
"largeImageUrl": renderers.TagValue(
artist.attachment_cover.download_url_large_square_crop
),
"biography": renderers.TagValue(artist.description.rendered),
}
assert serializers.GetArtistInfo2Serializer(artist).data == expected
@pytest.mark.parametrize(
"mimetype, extension, expected",
[
@ -184,6 +205,9 @@ def test_get_album_serializer(factories):
"year": album.release_date.year,
"coverArt": f"al-{album.id}",
"genre": tagged_item.tag.name,
"genres": [{"name": tagged_item.tag.name}],
"mediaType": "album",
"musicBrainzId": album.mbid,
"duration": 43,
"playCount": album.tracks.aggregate(l=Count("listenings"))["l"] or 0,
"song": [
@ -200,13 +224,15 @@ def test_get_album_serializer(factories):
"contentType": upload.mimetype,
"suffix": upload.extension or "",
"path": serializers.get_track_path(track, upload.extension),
"bitrate": 42,
"bitRate": 42,
"duration": 43,
"size": 44,
"created": serializers.to_subsonic_date(track.creation_date),
"albumId": album.pk,
"artistId": artist.pk,
"type": "music",
"mediaType": "song",
"musicBrainzId": track.mbid,
}
],
}
@ -341,7 +367,7 @@ def test_channel_episode_serializer(factories):
"genre": "Podcast",
"size": upload.size,
"duration": upload.duration,
"bitrate": upload.bitrate / 1000,
"bitRate": upload.bitrate / 1000,
"contentType": upload.mimetype,
"suffix": upload.extension,
"status": "completed",

Wyświetl plik

@ -97,6 +97,23 @@ def test_ping(f, db, api_client):
assert response.data == expected
@pytest.mark.parametrize("f", ["xml", "json"])
def test_get_open_subsonic_extensions(f, db, api_client):
url = reverse("api:subsonic:subsonic-get_open_subsonic_extensions")
response = api_client.get(url, {"f": f})
expected = {
"openSubsonicExtensions": [
{
"name": "formPost",
"versions": [1],
}
],
}
assert response.status_code == 200
assert response.data == expected
@pytest.mark.parametrize("f", ["json"])
def test_get_artists(
f, db, logged_in_api_client, factories, mocker, queryset_equal_queries
@ -166,7 +183,11 @@ def test_get_artist_info2(
artist = factories["music.Artist"](playable=True)
playable_by = mocker.spy(music_models.ArtistQuerySet, "playable_by")
expected = {"artist-info2": {}}
expected = {
"artistInfo2": {
"musicBrainzId": artist.mbid,
}
}
response = logged_in_api_client.get(url, {"id": artist.pk})
assert response.status_code == 200
@ -592,7 +613,7 @@ def test_search3(f, db, logged_in_api_client, factories):
artist_qs = (
music_models.Artist.objects.with_albums_count()
.filter(pk=artist.pk)
.values("_albums_count", "id", "name")
.values("_albums_count", "id", "name", "mbid")
)
assert response.status_code == 200
assert response.data == {

Wyświetl plik

@ -12,5 +12,5 @@ def test_can_resolve_subsonic():
def test_can_resolve_v2():
path = reverse("api:v2:instance:nodeinfo-2.0")
assert path == "/api/v2/instance/nodeinfo/2.0"
path = reverse("api:v2:instance:nodeinfo-2.1")
assert path == "/api/v2/instance/nodeinfo/2.1"

Wyświetl plik

@ -8,7 +8,27 @@ from funkwhale_api.moderation import tasks as moderation_tasks
from funkwhale_api.users.models import User
def test_can_create_user_via_api(preferences, api_client, db):
def test_can_create_user_via_api(settings, preferences, api_client, db):
url = reverse("rest_register")
data = {
"username": "test1",
"email": "test1@test.com",
"password1": "thisismypassword",
"password2": "thisismypassword",
}
preferences["users__registration_enabled"] = True
settings.ACCOUNT_EMAIL_VERIFICATION = "mandatory"
response = api_client.post(url, data)
assert response.status_code == 201
assert response.data["detail"] == "Verification e-mail sent."
u = User.objects.get(email="test1@test.com")
assert u.username == "test1"
def test_can_create_user_via_api_mail_verification_mandatory(
settings, preferences, api_client, db
):
url = reverse("rest_register")
data = {
"username": "test1",
@ -18,7 +38,7 @@ def test_can_create_user_via_api(preferences, api_client, db):
}
preferences["users__registration_enabled"] = True
response = api_client.post(url, data)
assert response.status_code == 201
assert response.status_code == 204
u = User.objects.get(email="test1@test.com")
assert u.username == "test1"
@ -82,7 +102,7 @@ def test_can_signup_with_invitation(preferences, factories, api_client):
}
preferences["users__registration_enabled"] = False
response = api_client.post(url, data)
assert response.status_code == 201
assert response.status_code == 204
u = User.objects.get(email="test1@test.com")
assert u.username == "test1"
assert u.invitation == invitation
@ -302,7 +322,7 @@ def test_creating_user_creates_actor_as_well(
mocker.patch("funkwhale_api.users.models.create_actor", return_value=actor)
response = api_client.post(url, data)
assert response.status_code == 201
assert response.status_code == 204
user = User.objects.get(username="test1")
@ -323,7 +343,7 @@ def test_creating_user_sends_confirmation_email(
preferences["instance__name"] = "Hello world"
response = api_client.post(url, data)
assert response.status_code == 201
assert response.status_code == 204
confirmation_message = mailoutbox[-1]
assert "Hello world" in confirmation_message.body
@ -405,7 +425,7 @@ def test_signup_with_approval_enabled(
}
on_commit = mocker.patch("funkwhale_api.common.utils.on_commit")
response = api_client.post(url, data, format="json")
assert response.status_code == 201
assert response.status_code == 204
u = User.objects.get(email="test1@test.com")
assert u.username == "test1"
assert u.is_active is False

Wyświetl plik

@ -1,3 +0,0 @@
Prohibit the creation of new users using django's `createsuperuser` command in favor of our own CLI
entry point. Run `funkwhale-manage fw users create --superuser` instead. (#1288)

Wyświetl plik

@ -1 +0,0 @@
Create a testing environment in production for ListenBrainz recommendation engine (troi-recommendation-playground) (#1861)

Some files were not shown because too many files have changed in this diff Show More