Compare commits

...

27 Commits

Author SHA1 Message Date
Charles-Henri BERNARD f0b3df0485
Merge fd2a0896c0 into a1cd71922f 2024-04-26 10:52:24 +01:00
Buster Neece a1cd71922f
Make Branding Config return in the API to take advantage of updated JSON serialization. 2024-04-25 12:48:53 -05:00
Buster Neece 1a68f34452
Set the station config classes to return `{}` when empty and serialized to JSON. 2024-04-25 12:48:03 -05:00
Buster Neece b1662a4757
Wire up `is_enabled` functionality for public podcast pages. 2024-04-25 12:47:26 -05:00
Buster Neece da51ec823c
Add custom HTML to Branding config object. 2024-04-25 12:46:49 -05:00
Buster Neece 3078f4f686
Add UI for enable and custom HTML in branding. 2024-04-25 12:45:28 -05:00
Buster Neece 66df8f7850
Add "is_enabled" flag to Podcasts. 2024-04-25 10:19:37 -05:00
Buster Neece 3f9c21b63c
Update NPM dependencies. 2024-04-22 10:29:56 -05:00
Buster Neece ac7dd73fd8
Update PHP dependencies.
Changelogs summary:

 - php-http/discovery updated from 1.19.2 to 1.19.4 patch
   See changes: https://github.com/php-http/discovery/compare/1.19.2...1.19.4
   Release notes: https://github.com/php-http/discovery/releases/tag/1.19.4

 - symfony/serializer updated from v7.0.4 to v7.0.6 patch
   See changes: https://github.com/symfony/serializer/compare/v7.0.4...v7.0.6
   Release notes: https://github.com/symfony/serializer/releases/tag/v7.0.6

 - symfony/var-exporter updated from v7.0.4 to v7.0.6 patch
   See changes: https://github.com/symfony/var-exporter/compare/v7.0.4...v7.0.6
   Release notes: https://github.com/symfony/var-exporter/releases/tag/v7.0.6

 - symfony/service-contracts updated from v3.4.1 to v3.4.2 patch
   See changes: https://github.com/symfony/service-contracts/compare/v3.4.1...v3.4.2
   Release notes: https://github.com/symfony/service-contracts/releases/tag/v3.4.2

 - symfony/console updated from v7.0.4 to v7.0.6 patch
   See changes: https://github.com/symfony/console/compare/v7.0.4...v7.0.6
   Release notes: https://github.com/symfony/console/releases/tag/v7.0.6

 - doctrine/collections updated from 2.2.1 to 2.2.2 patch
   See changes: https://github.com/doctrine/collections/compare/2.2.1...2.2.2
   Release notes: https://github.com/doctrine/collections/releases/tag/2.2.2

 - doctrine/orm updated from 3.1.0 to 3.1.2 patch
   See changes: https://github.com/doctrine/orm/compare/3.1.0...3.1.2
   Release notes: https://github.com/doctrine/orm/releases/tag/3.1.2

 - br33f/php-ga4-mp updated from v0.1.4 to v0.1.5 patch
   See changes: https://github.com/br33f/php-GA4-Measurement-Protocol/compare/v0.1.4...v0.1.5
   Release notes: https://github.com/br33f/php-GA4-Measurement-Protocol/releases/tag/v0.1.5

 - symfony/var-dumper updated from v7.0.4 to v7.0.6 patch
   See changes: https://github.com/symfony/var-dumper/compare/v7.0.4...v7.0.6
   Release notes: https://github.com/symfony/var-dumper/releases/tag/v7.0.6

 - symfony/event-dispatcher-contracts updated from v3.4.0 to v3.4.2 patch
   See changes: https://github.com/symfony/event-dispatcher-contracts/compare/v3.4.0...v3.4.2
   Release notes: https://github.com/symfony/event-dispatcher-contracts/releases/tag/v3.4.2

 - psy/psysh updated from v0.12.2 to v0.12.3 patch
   See changes: https://github.com/bobthecow/psysh/compare/v0.12.2...v0.12.3
   Release notes: https://github.com/bobthecow/psysh/releases/tag/v0.12.3

 - sebastian/environment updated from 7.0.0 to 7.1.0 minor
   See changes: https://github.com/sebastianbergmann/environment/compare/7.0.0...7.1.0
   Release notes: https://github.com/sebastianbergmann/environment/releases/tag/7.1.0

 - phpunit/phpunit updated from 11.0.6 to 11.1.2 minor
   See changes: https://github.com/sebastianbergmann/phpunit/compare/11.0.6...11.1.2
   Release notes: https://github.com/sebastianbergmann/phpunit/releases/tag/11.1.2

 - masterminds/html5 updated from 2.8.1 to 2.9.0 minor
   See changes: https://github.com/Masterminds/html5-php/compare/2.8.1...2.9.0
   Release notes: https://github.com/Masterminds/html5-php/releases/tag/2.9.0

 - intervention/gif updated from 4.0.2 to 4.1.0 minor
   See changes: https://github.com/Intervention/gif/compare/4.0.2...4.1.0
   Release notes: https://github.com/Intervention/gif/releases/tag/4.1.0

 - intervention/image updated from 3.5.0 to 3.6.0 minor
   See changes: https://github.com/Intervention/image/compare/3.5.0...3.6.0
   Release notes: https://github.com/Intervention/image/releases/tag/3.6.0

 - league/flysystem updated from 3.25.1 to 3.27.0 minor
   See changes: https://github.com/thephpleague/flysystem/compare/3.25.1...3.27.0
   Release notes: https://github.com/thephpleague/flysystem/releases/tag/3.27.0

 - aws/aws-sdk-php updated from 3.301.3 to 3.304.8 minor
   See changes: https://github.com/aws/aws-sdk-php/compare/3.301.3...3.304.8
   Release notes: https://github.com/aws/aws-sdk-php/releases/tag/3.304.8

 - league/flysystem-aws-s3-v3 updated from 3.25.1 to 3.27.0 minor
   See changes: https://github.com/thephpleague/flysystem-aws-s3-v3/compare/3.25.1...3.27.0
   Release notes: https://github.com/thephpleague/flysystem-aws-s3-v3/releases/tag/3.27.0

 - league/flysystem-sftp-v3 updated from 3.25.1 to 3.26.0 minor
   See changes: https://github.com/thephpleague/flysystem-sftp-v3/compare/3.25.1...3.26.0
   Release notes: https://github.com/thephpleague/flysystem-sftp-v3/releases/tag/3.26.0

 - matomo/device-detector updated from 6.3.0 to 6.3.1 patch
   See changes: https://github.com/matomo-org/device-detector/compare/6.3.0...6.3.1
   Release notes: https://github.com/matomo-org/device-detector/releases/tag/6.3.1

 - mockery/mockery updated from 1.6.10 to 1.6.11 patch
   See changes: https://github.com/mockery/mockery/compare/1.6.10...1.6.11
   Release notes: https://github.com/mockery/mockery/releases/tag/1.6.11

 - monolog/monolog updated from 3.5.0 to 3.6.0 minor
   See changes: https://github.com/Seldaek/monolog/compare/3.5.0...3.6.0
   Release notes: https://github.com/Seldaek/monolog/releases/tag/3.6.0

 - symfony/translation-contracts updated from v3.4.1 to v3.4.2 patch
   See changes: https://github.com/symfony/translation-contracts/compare/v3.4.1...v3.4.2
   Release notes: https://github.com/symfony/translation-contracts/releases/tag/v3.4.2

 - nesbot/carbon updated from 3.1.1 to 3.3.0 minor
   See changes: https://github.com/briannesbitt/Carbon/compare/3.1.1...3.3.0
   Release notes: https://github.com/briannesbitt/Carbon/releases/tag/3.3.0

 - pagerfanta/core updated from v4.3.2 to v4.5.0 minor
   See changes: https://github.com/Pagerfanta/core/compare/v4.3.2...v4.5.0
   Release notes: https://github.com/Pagerfanta/core/releases/tag/v4.5.0

 - pagerfanta/doctrine-collections-adapter updated from v4.3.2 to v4.5.0 minor
   See changes: https://github.com/Pagerfanta/doctrine-collections-adapter/compare/v4.3.2...v4.5.0
   Release notes: https://github.com/Pagerfanta/doctrine-collections-adapter/releases/tag/v4.5.0

 - pagerfanta/doctrine-orm-adapter updated from v4.3.2 to v4.5.0 minor
   See changes: https://github.com/Pagerfanta/doctrine-orm-adapter/compare/v4.3.2...v4.5.0
   Release notes: https://github.com/Pagerfanta/doctrine-orm-adapter/releases/tag/v4.5.0

 - symfony/cache-contracts updated from v3.4.0 to v3.4.2 patch
   See changes: https://github.com/symfony/cache-contracts/compare/v3.4.0...v3.4.2
   Release notes: https://github.com/symfony/cache-contracts/releases/tag/v3.4.2

 - symfony/cache updated from v7.0.4 to v7.0.6 patch
   See changes: https://github.com/symfony/cache/compare/v7.0.4...v7.0.6
   Release notes: https://github.com/symfony/cache/releases/tag/v7.0.6

 - php-parallel-lint/php-parallel-lint updated from v1.3.2 to v1.4.0 minor
   See changes: https://github.com/php-parallel-lint/PHP-Parallel-Lint/compare/v1.3.2...v1.4.0
   Release notes: https://github.com/php-parallel-lint/PHP-Parallel-Lint/releases/tag/v1.4.0

 - phpstan/phpstan updated from 1.10.63 to 1.10.67 patch
   See changes: https://github.com/phpstan/phpstan/compare/1.10.63...1.10.67
   Release notes: https://github.com/phpstan/phpstan/releases/tag/1.10.67

 - phpstan/phpstan-doctrine updated from 1.3.63 to 1.3.69 patch
   See changes: https://github.com/phpstan/phpstan-doctrine/compare/1.3.63...1.3.69
   Release notes: https://github.com/phpstan/phpstan-doctrine/releases/tag/1.3.69

 - spiral/roadrunner updated from v2023.3.12 to v2024.1.0 major
   See changes: https://github.com/roadrunner-server/roadrunner/compare/v2023.3.12...v2024.1.0
   Release notes: https://github.com/roadrunner-server/roadrunner/releases/tag/v2024.1.0

 - spiral/goridge updated from 4.1.1 to v4.2.0 minor
   See changes: https://github.com/roadrunner-php/goridge/compare/4.1.1...v4.2.0
   Release notes: https://github.com/roadrunner-php/goridge/releases/tag/v4.2.0

 - spiral/roadrunner-worker updated from v3.4.0 to v3.5.0 minor
   See changes: https://github.com/roadrunner-php/worker/compare/v3.4.0...v3.5.0
   Release notes: https://github.com/roadrunner-php/worker/releases/tag/v3.5.0

 - roadrunner-php/roadrunner-api-dto updated from v1.5.0 to v1.7.0 minor
   See changes: https://github.com/roadrunner-php/roadrunner-api-dto/compare/v1.5.0...v1.7.0
   Release notes: https://github.com/roadrunner-php/roadrunner-api-dto/releases/tag/v1.7.0

 - roadrunner-php/centrifugo updated from v2.0.1 to v2.1.0 minor
   See changes: https://github.com/roadrunner-php/centrifugo/compare/v2.0.1...v2.1.0
   Release notes: https://github.com/roadrunner-php/centrifugo/releases/tag/v2.1.0

 - roave/security-advisories updated from dev-latest@04b9d7f to dev-latest@a6fb2a7
   See changes: 04b9d7f...a6fb2a7

 - squizlabs/php_codesniffer updated from 3.9.0 to 3.9.1 patch
   See changes: https://github.com/PHPCSStandards/PHP_CodeSniffer/compare/3.9.0...3.9.1
   Release notes: https://github.com/PHPCSStandards/PHP_CodeSniffer/releases/tag/3.9.1

 - phpstan/phpdoc-parser updated from 1.26.0 to 1.28.0 minor
   See changes: https://github.com/phpstan/phpdoc-parser/compare/1.26.0...1.28.0
   Release notes: https://github.com/phpstan/phpdoc-parser/releases/tag/1.28.0

 - spiral/roadrunner-http updated from v3.4.0 to v3.5.0 minor
   See changes: https://github.com/roadrunner-php/http/compare/v3.4.0...v3.5.0
   Release notes: https://github.com/roadrunner-php/http/releases/tag/v3.5.0

 - spomky-labs/otphp updated from 11.2.0 to 11.2.2 patch
   See changes: https://github.com/Spomky-Labs/otphp/compare/11.2.0...11.2.2
   Release notes: https://github.com/Spomky-Labs/otphp/releases/tag/11.2.2

 - symfony/filesystem updated from v7.0.3 to v7.0.6 patch
   See changes: https://github.com/symfony/filesystem/compare/v7.0.3...v7.0.6
   Release notes: https://github.com/symfony/filesystem/releases/tag/v7.0.6

 - symfony/lock updated from v7.0.3 to v7.0.6 patch
   See changes: https://github.com/symfony/lock/compare/v7.0.3...v7.0.6
   Release notes: https://github.com/symfony/lock/releases/tag/v7.0.6

 - symfony/mime updated from v7.0.3 to v7.0.6 patch
   See changes: https://github.com/symfony/mime/compare/v7.0.3...v7.0.6
   Release notes: https://github.com/symfony/mime/releases/tag/v7.0.6

 - symfony/mailer updated from v7.0.4 to v7.0.6 patch
   See changes: https://github.com/symfony/mailer/compare/v7.0.4...v7.0.6
   Release notes: https://github.com/symfony/mailer/releases/tag/v7.0.6

 - symfony/property-info updated from v7.0.3 to v7.0.6 patch
   See changes: https://github.com/symfony/property-info/compare/v7.0.3...v7.0.6
   Release notes: https://github.com/symfony/property-info/releases/tag/v7.0.6

 - symfony/property-access updated from v7.0.4 to v7.0.6 patch
   See changes: https://github.com/symfony/property-access/compare/v7.0.4...v7.0.6
   Release notes: https://github.com/symfony/property-access/releases/tag/v7.0.6

 - symfony/messenger updated from v7.0.4 to v7.0.6 patch
   See changes: https://github.com/symfony/messenger/compare/v7.0.4...v7.0.6
   Release notes: https://github.com/symfony/messenger/releases/tag/v7.0.6

 - symfony/validator updated from v7.0.5 to v7.0.6 patch
   See changes: https://github.com/symfony/validator/compare/v7.0.5...v7.0.6
   Release notes: https://github.com/symfony/validator/releases/tag/v7.0.6

 - zircote/swagger-php updated from dev-master@d3265da to dev-master@b46a36d
   See changes: d3265da...b46a36d

No security vulnerability advisories found.
2024-04-22 09:28:36 -05:00
Buster Neece bcb4443b4d
Switch from Codeception Doctrine 2 module to newer Doctrine module. 2024-04-22 09:19:47 -05:00
Buster Neece 6feae35d81
Fix links in Podcast RSS feeds; add "podcast:guid" calculated from global podcast namespace. 2024-04-21 10:23:53 -05:00
Buster Neece f12b3c0da2
Finish removing MarcW RSS writer components. 2024-04-21 09:52:28 -05:00
Buster Neece d03dc1f277
Update the Podcast RSS feed to write raw XML instead of using an RSS library; add iTunes season/episode. 2024-04-21 09:46:47 -05:00
Buster Neece ba4a71cd98
Update XML writer to support writing root element text and attributes (the reverse of what Reader does). 2024-04-21 09:46:20 -05:00
Buster Neece 820cc7ad03
Make XML writer support excluding opening tag in the writing process. 2024-04-21 07:09:28 -05:00
Buster Neece d438be0a72
Add admin panel UI for season/episode numbers. 2024-04-21 05:19:37 -05:00
Buster Neece 1871b7a0cd
Code style cleanup fixes. 2024-04-21 05:01:06 -05:00
Buster Neece 17e83547f7
Update API spec and API interfaces with new fields. 2024-04-21 04:55:04 -05:00
Buster Neece c881a28be4
Update Podcast to add branding config; update PodcastEpisode to add season and episode numbers. 2024-04-21 04:54:21 -05:00
Buster Neece c84522105d
Slightly modify DB migration template. 2024-04-21 04:47:32 -05:00
Charles-Henri BERNARD fd2a0896c0
Merge branch 'main' into main 2024-02-02 15:28:55 +01:00
Charles-Henri BERNARD 7ba5e5a437
Update ConfigWriter.php 2024-01-31 11:32:15 +01:00
Charles-Henri BERNARD 9aef2cf2d2
Update StationPortCheckerValidator.php 2024-01-31 11:07:08 +01:00
Charles-Henri BERNARD e49db10137
Update Configuration.php 2024-01-31 11:06:04 +01:00
Charles-Henri BERNARD 99c288a072
Update StreamersForm.vue 2024-01-31 11:04:02 +01:00
Charles-Henri BERNARD 460c49b01d
Update StationBackendConfiguration.php 2024-01-31 10:58:45 +01:00
Charles-Henri BERNARD 61db6e5735
Update Liquidsoap.php 2024-01-31 10:57:44 +01:00
36 changed files with 1246 additions and 865 deletions

View File

@ -52,7 +52,6 @@
"league/oauth2-client": "^2.6",
"league/plates": "^3.1",
"lstrojny/fxmlrpc": "dev-master",
"marcw/rss-writer": "^0.4.0",
"matomo/device-detector": "^6",
"mezzio/mezzio-session": "^1.3",
"mezzio/mezzio-session-cache": "^1.7",
@ -114,7 +113,7 @@
"codeception/codeception": "^5.0.0-RC1",
"codeception/module-asserts": "^3",
"codeception/module-cli": "^2",
"codeception/module-doctrine2": "^3",
"codeception/module-doctrine": "^3.1",
"codeception/module-phpbrowser": "dev-master",
"codeception/module-rest": "^3",
"filp/whoops": "^2",

843
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -8,6 +8,7 @@
"license": "AGPL-3.0",
"dependencies": {
"@codemirror/lang-css": "^6.0.1",
"@codemirror/lang-html": "^6.4.9",
"@codemirror/lang-javascript": "^6.1.2",
"@flowjs/flow.js": "^2.14.1",
"@fullcalendar/bootstrap5": "^6.1.8",
@ -209,9 +210,9 @@
}
},
"node_modules/@codemirror/commands": {
"version": "6.3.3",
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.3.3.tgz",
"integrity": "sha512-dO4hcF0fGT9tu1Pj1D2PvGvxjeGkbC6RGcZw6Qs74TH+Ed1gw98jmUgd2axWvIZEqTeTuFrg1lEB1KV6cK9h1A==",
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.5.0.tgz",
"integrity": "sha512-rK+sj4fCAN/QfcY9BEzYMgp4wwL/q5aj/VfNSoH1RWPF9XS/dUwBkvlL3hpWgEjOqlpdN1uLC9UkjJ4tmyjJYg==",
"dependencies": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.4.0",
@ -231,6 +232,22 @@
"@lezer/css": "^1.0.0"
}
},
"node_modules/@codemirror/lang-html": {
"version": "6.4.9",
"resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.9.tgz",
"integrity": "sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q==",
"dependencies": {
"@codemirror/autocomplete": "^6.0.0",
"@codemirror/lang-css": "^6.0.0",
"@codemirror/lang-javascript": "^6.0.0",
"@codemirror/language": "^6.4.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.17.0",
"@lezer/common": "^1.0.0",
"@lezer/css": "^1.1.0",
"@lezer/html": "^1.3.0"
}
},
"node_modules/@codemirror/lang-javascript": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.2.tgz",
@ -932,6 +949,16 @@
"@lezer/common": "^1.0.0"
}
},
"node_modules/@lezer/html": {
"version": "1.3.9",
"resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.9.tgz",
"integrity": "sha512-MXxeCMPyrcemSLGaTQEZx0dBUH0i+RPl8RN5GwMAzo53nTsd/Unc/t5ZxACeQoyPUM5/GkPLRUs2WliOImzkRA==",
"dependencies": {
"@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.0.0"
}
},
"node_modules/@lezer/javascript": {
"version": "1.4.14",
"resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.14.tgz",
@ -1017,9 +1044,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.14.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.3.tgz",
"integrity": "sha512-X9alQ3XM6I9IlSlmC8ddAvMSyG1WuHk5oUnXGw+yUBs3BFoTizmG1La/Gr8fVJvDWAq+zlYTZ9DBgrlKRVY06g==",
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.16.1.tgz",
"integrity": "sha512-92/y0TqNLRYOTXpm6Z7mnpvKAG9P7qmK7yJeRJSdzElNCUnsgbpAsGqerUboYRIQKzgfq4pWu9xVkgpWLfmNsw==",
"cpu": [
"arm"
],
@ -1030,9 +1057,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.14.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.14.3.tgz",
"integrity": "sha512-eQK5JIi+POhFpzk+LnjKIy4Ks+pwJ+NXmPxOCSvOKSNRPONzKuUvWE+P9JxGZVxrtzm6BAYMaL50FFuPe0oWMQ==",
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.16.1.tgz",
"integrity": "sha512-ttWB6ZCfRLuDIUiE0yiu5gcqOsYjA5F7kEV1ggHMj20FwLZ8A1FMeahZJFl/pnOmcnD2QL0z4AcDuo27utGU8A==",
"cpu": [
"arm64"
],
@ -1043,9 +1070,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.14.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.14.3.tgz",
"integrity": "sha512-Od4vE6f6CTT53yM1jgcLqNfItTsLt5zE46fdPaEmeFHvPs5SjZYlLpHrSiHEKR1+HdRfxuzXHjDOIxQyC3ptBA==",
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.16.1.tgz",
"integrity": "sha512-QLDvPLetbqjHojTGFw9+nuSP3YY/iz2k1cep6crYlr97sS+ZJ0W43b8Z0zC00+lnFZj6JSNxiA4DjboNQMuh1A==",
"cpu": [
"arm64"
],
@ -1056,9 +1083,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.14.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.14.3.tgz",
"integrity": "sha512-0IMAO21axJeNIrvS9lSe/PGthc8ZUS+zC53O0VhF5gMxfmcKAP4ESkKOCwEi6u2asUrt4mQv2rjY8QseIEb1aw==",
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.16.1.tgz",
"integrity": "sha512-TAUK/D8khRrRIa1KwRzo8JNKk3tcqaeXWdtsiLgA8zmACWwlWLjPCJ4DULGHQrMkeBjp1Cd3Yuwx04lZgFx5Vg==",
"cpu": [
"x64"
],
@ -1069,9 +1096,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.14.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.14.3.tgz",
"integrity": "sha512-ge2DC7tHRHa3caVEoSbPRJpq7azhG+xYsd6u2MEnJ6XzPSzQsTKyXvh6iWjXRf7Rt9ykIUWHtl0Uz3T6yXPpKw==",
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.16.1.tgz",
"integrity": "sha512-KO+WGZjrh6zyFTD1alIFkfdtxf8B4BC+hqd3kBZHscPLvE5FR/6QKsyuCT0JlERxxYBSUKNUQ/UHyX5uwO1x2A==",
"cpu": [
"arm"
],
@ -1082,9 +1109,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.14.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.14.3.tgz",
"integrity": "sha512-ljcuiDI4V3ySuc7eSk4lQ9wU8J8r8KrOUvB2U+TtK0TiW6OFDmJ+DdIjjwZHIw9CNxzbmXY39wwpzYuFDwNXuw==",
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.16.1.tgz",
"integrity": "sha512-NqxbllzIB1WoAo4ThUXVtd21iiM5IHMTTXmXySKBLVcZvkU0HIZmatlP7hLzb5yQubcmdIeWmncd2NdsjocEiw==",
"cpu": [
"arm"
],
@ -1095,9 +1122,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.14.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.14.3.tgz",
"integrity": "sha512-Eci2us9VTHm1eSyn5/eEpaC7eP/mp5n46gTRB3Aar3BgSvDQGJZuicyq6TsH4HngNBgVqC5sDYxOzTExSU+NjA==",
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.16.1.tgz",
"integrity": "sha512-snma5NvV8y7IECQ5rq0sr0f3UUu+92NVmG/913JXJMcXo84h9ak9TA5UI9Cl2XRM9j3m37QwDBtEYnJzRkSmxA==",
"cpu": [
"arm64"
],
@ -1108,9 +1135,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.14.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.14.3.tgz",
"integrity": "sha512-UrBoMLCq4E92/LCqlh+blpqMz5h1tJttPIniwUgOFJyjWI1qrtrDhhpHPuFxULlUmjFHfloWdixtDhSxJt5iKw==",
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.16.1.tgz",
"integrity": "sha512-KOvqGprlD84ueivhCi2flvcUwDRD20mAsE3vxQNVEI2Di9tnPGAfEu6UcrSPZbM+jG2w1oSr43hrPo0RNg6GGg==",
"cpu": [
"arm64"
],
@ -1121,9 +1148,9 @@
]
},
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.14.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.14.3.tgz",
"integrity": "sha512-5aRjvsS8q1nWN8AoRfrq5+9IflC3P1leMoy4r2WjXyFqf3qcqsxRCfxtZIV58tCxd+Yv7WELPcO9mY9aeQyAmw==",
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.16.1.tgz",
"integrity": "sha512-/gsNwtiGLqYwN4vP+EIdUC6Q6LTlpupWqokqIndvZcjn9ig/5P01WyaYCU2wvfL/2Z82jp5kX8c1mDBOvCP3zg==",
"cpu": [
"ppc64"
],
@ -1134,9 +1161,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.14.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.14.3.tgz",
"integrity": "sha512-sk/Qh1j2/RJSX7FhEpJn8n0ndxy/uf0kI/9Zc4b1ELhqULVdTfN6HL31CDaTChiBAOgLcsJ1sgVZjWv8XNEsAQ==",
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.16.1.tgz",
"integrity": "sha512-uU8zuGkQfGqfD9w6VRJZI4IuG4JIfNxxJgEmLMAmPVHREKGsxFVfgHy5c6CexQF2vOfgjB33OsET3Vdn2lln9A==",
"cpu": [
"riscv64"
],
@ -1147,9 +1174,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.14.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.14.3.tgz",
"integrity": "sha512-jOO/PEaDitOmY9TgkxF/TQIjXySQe5KVYB57H/8LRP/ux0ZoO8cSHCX17asMSv3ruwslXW/TLBcxyaUzGRHcqg==",
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.16.1.tgz",
"integrity": "sha512-lsjLtDgtcGFEuBP6yrXwkRN5/wKlvUZtfbKZZu0yaoNpiBL4epgnO21osAALIspVRnl4qZgyLFd8xjCYYWgwfw==",
"cpu": [
"s390x"
],
@ -1160,9 +1187,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.14.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.14.3.tgz",
"integrity": "sha512-8ybV4Xjy59xLMyWo3GCfEGqtKV5M5gCSrZlxkPGvEPCGDLNla7v48S662HSGwRd6/2cSneMQWiv+QzcttLrrOA==",
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.16.1.tgz",
"integrity": "sha512-N2ZizKhUryqqrMfdCnjhJhZRgv61C6gK+hwVtCIKC8ts8J+go+vqENnGexwg21nHIOvLN5mBM8a7DI2vlyIOPg==",
"cpu": [
"x64"
],
@ -1173,9 +1200,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.14.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.14.3.tgz",
"integrity": "sha512-s+xf1I46trOY10OqAtZ5Rm6lzHre/UiLA1J2uOhCFXWkbZrJRkYBPO6FhvGfHmdtQ3Bx793MNa7LvoWFAm93bg==",
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.16.1.tgz",
"integrity": "sha512-5ICeMxqg66FrOA2AbnBQ2TJVxfvZsKLxmof0ibvPLaYtbsJqnTUtJOofgWb46Gjd4uZcA4rdsp4JCxegzQPqCg==",
"cpu": [
"x64"
],
@ -1186,9 +1213,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.14.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.14.3.tgz",
"integrity": "sha512-+4h2WrGOYsOumDQ5S2sYNyhVfrue+9tc9XcLWLh+Kw3UOxAvrfOrSMFon60KspcDdytkNDh7K2Vs6eMaYImAZg==",
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.16.1.tgz",
"integrity": "sha512-1vIP6Ce02L+qWD7uZYRiFiuAJo3m9kARatWmFSnss0gZnVj2Id7OPUU9gm49JPGasgcR3xMqiH3fqBJ8t00yVg==",
"cpu": [
"arm64"
],
@ -1199,9 +1226,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.14.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.14.3.tgz",
"integrity": "sha512-T1l7y/bCeL/kUwh9OD4PQT4aM7Bq43vX05htPJJ46RTI4r5KNt6qJRzAfNfM+OYMNEVBWQzR2Gyk+FXLZfogGw==",
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.16.1.tgz",
"integrity": "sha512-Y3M92DcVsT6LoP+wrKpoUWPaazaP1fzbNkp0a0ZSj5Y//+pQVfVe/tQdsYQQy7dwXR30ZfALUIc9PCh9Izir6w==",
"cpu": [
"ia32"
],
@ -1212,9 +1239,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.14.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.3.tgz",
"integrity": "sha512-/BypzV0H1y1HzgYpxqRaXGBRqfodgoBBCcsrujT6QRcakDQdfU+Lq9PENPh5jB4I44YWq+0C2eHsHya+nZY1sA==",
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.16.1.tgz",
"integrity": "sha512-x0fvpHMuF7fK5r8oZxSi8VYXkrVmRgubXpO/wcf15Lk3xZ4Jvvh5oG+u7Su1776A7XzVKZhD2eRc4t7H50gL3w==",
"cpu": [
"x64"
],
@ -1246,9 +1273,9 @@
}
},
"node_modules/@types/eslint": {
"version": "8.56.9",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.9.tgz",
"integrity": "sha512-W4W3KcqzjJ0sHg2vAq9vfml6OhsJ53TcUjUqfzzZf/EChUtwspszj/S0pzMxnfRcO55/iGq47dscXw71Fxc4Zg==",
"version": "8.56.10",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz",
"integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==",
"dev": true,
"dependencies": {
"@types/estree": "*",
@ -1283,9 +1310,9 @@
"dev": true
},
"node_modules/@types/leaflet": {
"version": "1.9.11",
"resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.11.tgz",
"integrity": "sha512-Cwqw9h+1LVnsuOutOSNHlIE1DBQ9U/0CCLfAkwtkgggrYRwf7SUlyZM0LQzngpwKyvFQbPLCf6hNMzly56pGZw==",
"version": "1.9.12",
"resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.12.tgz",
"integrity": "sha512-BK7XS+NyRI291HIo0HCfE18Lp8oA30H1gpi1tf0mF3TgiCEzanQjOqNZ4x126SXzzi2oNSZhZ5axJp1k0iM6jg==",
"dev": true,
"dependencies": {
"@types/geojson": "*"
@ -1348,16 +1375,16 @@
"integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow=="
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.6.0.tgz",
"integrity": "sha512-gKmTNwZnblUdnTIJu3e9kmeRRzV2j1a/LUO27KNNAnIC5zjy1aSvXSRp4rVNlmAoHlQ7HzX42NbKpcSr4jF80A==",
"version": "7.7.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.7.0.tgz",
"integrity": "sha512-GJWR0YnfrKnsRoluVO3PRb9r5aMZriiMMM/RHj5nnTrBy1/wIgk76XCtCKcnXGjpZQJQRFtGV9/0JJ6n30uwpQ==",
"dev": true,
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "7.6.0",
"@typescript-eslint/type-utils": "7.6.0",
"@typescript-eslint/utils": "7.6.0",
"@typescript-eslint/visitor-keys": "7.6.0",
"@typescript-eslint/scope-manager": "7.7.0",
"@typescript-eslint/type-utils": "7.7.0",
"@typescript-eslint/utils": "7.7.0",
"@typescript-eslint/visitor-keys": "7.7.0",
"debug": "^4.3.4",
"graphemer": "^1.4.0",
"ignore": "^5.3.1",
@ -1383,15 +1410,15 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.6.0.tgz",
"integrity": "sha512-usPMPHcwX3ZoPWnBnhhorc14NJw9J4HpSXQX4urF2TPKG0au0XhJoZyX62fmvdHONUkmyUe74Hzm1//XA+BoYg==",
"version": "7.7.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.7.0.tgz",
"integrity": "sha512-fNcDm3wSwVM8QYL4HKVBggdIPAy9Q41vcvC/GtDobw3c4ndVT3K6cqudUmjHPw8EAp4ufax0o58/xvWaP2FmTg==",
"dev": true,
"dependencies": {
"@typescript-eslint/scope-manager": "7.6.0",
"@typescript-eslint/types": "7.6.0",
"@typescript-eslint/typescript-estree": "7.6.0",
"@typescript-eslint/visitor-keys": "7.6.0",
"@typescript-eslint/scope-manager": "7.7.0",
"@typescript-eslint/types": "7.7.0",
"@typescript-eslint/typescript-estree": "7.7.0",
"@typescript-eslint/visitor-keys": "7.7.0",
"debug": "^4.3.4"
},
"engines": {
@ -1411,13 +1438,13 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.6.0.tgz",
"integrity": "sha512-ngttyfExA5PsHSx0rdFgnADMYQi+Zkeiv4/ZxGYUWd0nLs63Ha0ksmp8VMxAIC0wtCFxMos7Lt3PszJssG/E6w==",
"version": "7.7.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.7.0.tgz",
"integrity": "sha512-/8INDn0YLInbe9Wt7dK4cXLDYp0fNHP5xKLHvZl3mOT5X17rK/YShXaiNmorl+/U4VKCVIjJnx4Ri5b0y+HClw==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "7.6.0",
"@typescript-eslint/visitor-keys": "7.6.0"
"@typescript-eslint/types": "7.7.0",
"@typescript-eslint/visitor-keys": "7.7.0"
},
"engines": {
"node": "^18.18.0 || >=20.0.0"
@ -1428,13 +1455,13 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.6.0.tgz",
"integrity": "sha512-NxAfqAPNLG6LTmy7uZgpK8KcuiS2NZD/HlThPXQRGwz6u7MDBWRVliEEl1Gj6U7++kVJTpehkhZzCJLMK66Scw==",
"version": "7.7.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.7.0.tgz",
"integrity": "sha512-bOp3ejoRYrhAlnT/bozNQi3nio9tIgv3U5C0mVDdZC7cpcQEDZXvq8inrHYghLVwuNABRqrMW5tzAv88Vy77Sg==",
"dev": true,
"dependencies": {
"@typescript-eslint/typescript-estree": "7.6.0",
"@typescript-eslint/utils": "7.6.0",
"@typescript-eslint/typescript-estree": "7.7.0",
"@typescript-eslint/utils": "7.7.0",
"debug": "^4.3.4",
"ts-api-utils": "^1.3.0"
},
@ -1455,9 +1482,9 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.6.0.tgz",
"integrity": "sha512-h02rYQn8J+MureCvHVVzhl69/GAfQGPQZmOMjG1KfCl7o3HtMSlPaPUAPu6lLctXI5ySRGIYk94clD/AUMCUgQ==",
"version": "7.7.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.7.0.tgz",
"integrity": "sha512-G01YPZ1Bd2hn+KPpIbrAhEWOn5lQBrjxkzHkWvP6NucMXFtfXoevK82hzQdpfuQYuhkvFDeQYbzXCjR1z9Z03w==",
"dev": true,
"engines": {
"node": "^18.18.0 || >=20.0.0"
@ -1468,13 +1495,13 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.6.0.tgz",
"integrity": "sha512-+7Y/GP9VuYibecrCQWSKgl3GvUM5cILRttpWtnAu8GNL9j11e4tbuGZmZjJ8ejnKYyBRb2ddGQ3rEFCq3QjMJw==",
"version": "7.7.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.7.0.tgz",
"integrity": "sha512-8p71HQPE6CbxIBy2kWHqM1KGrC07pk6RJn40n0DSc6bMOBBREZxSDJ+BmRzc8B5OdaMh1ty3mkuWRg4sCFiDQQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "7.6.0",
"@typescript-eslint/visitor-keys": "7.6.0",
"@typescript-eslint/types": "7.7.0",
"@typescript-eslint/visitor-keys": "7.7.0",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
@ -1496,17 +1523,17 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.6.0.tgz",
"integrity": "sha512-x54gaSsRRI+Nwz59TXpCsr6harB98qjXYzsRxGqvA5Ue3kQH+FxS7FYU81g/omn22ML2pZJkisy6Q+ElK8pBCA==",
"version": "7.7.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.7.0.tgz",
"integrity": "sha512-LKGAXMPQs8U/zMRFXDZOzmMKgFv3COlxUQ+2NMPhbqgVm6R1w+nU1i4836Pmxu9jZAuIeyySNrN/6Rc657ggig==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"@types/json-schema": "^7.0.15",
"@types/semver": "^7.5.8",
"@typescript-eslint/scope-manager": "7.6.0",
"@typescript-eslint/types": "7.6.0",
"@typescript-eslint/typescript-estree": "7.6.0",
"@typescript-eslint/scope-manager": "7.7.0",
"@typescript-eslint/types": "7.7.0",
"@typescript-eslint/typescript-estree": "7.7.0",
"semver": "^7.6.0"
},
"engines": {
@ -1521,12 +1548,12 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.6.0.tgz",
"integrity": "sha512-4eLB7t+LlNUmXzfOu1VAIAdkjbu5xNSerURS9X/S5TUKWFRpXRQZbmtPqgKmYx8bj3J0irtQXSiWAOY82v+cgw==",
"version": "7.7.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.7.0.tgz",
"integrity": "sha512-h0WHOj8MhdhY8YWkzIF30R379y0NqyOHExI9N9KCzvmu05EgG4FumeYa3ccfKUSphyWkWQE1ybVrgz/Pbam6YA==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "7.6.0",
"@typescript-eslint/types": "7.7.0",
"eslint-visitor-keys": "^3.4.3"
},
"engines": {
@ -1557,77 +1584,77 @@
}
},
"node_modules/@volar/language-core": {
"version": "2.2.0-alpha.8",
"resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.2.0-alpha.8.tgz",
"integrity": "sha512-Ew1Iw7/RIRNuDLn60fWJdOLApAlfTVPxbPiSLzc434PReC9kleYtaa//Wo2WlN1oiRqneW0pWQQV0CwYqaimLQ==",
"version": "2.2.0-alpha.10",
"resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.2.0-alpha.10.tgz",
"integrity": "sha512-njVJLtpu0zMvDaEk7K5q4BRpOgbyEUljU++un9TfJoJNhxG0z/hWwpwgTRImO42EKvwIxF3XUzeMk+qatAFy7Q==",
"dev": true,
"dependencies": {
"@volar/source-map": "2.2.0-alpha.8"
"@volar/source-map": "2.2.0-alpha.10"
}
},
"node_modules/@volar/source-map": {
"version": "2.2.0-alpha.8",
"resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.2.0-alpha.8.tgz",
"integrity": "sha512-E1ZVmXFJ5DU4fWDcWHzi8OLqqReqIDwhXvIMhVdk6+VipfMVv4SkryXu7/rs4GA/GsebcRyJdaSkKBB3OAkIcA==",
"version": "2.2.0-alpha.10",
"resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.2.0-alpha.10.tgz",
"integrity": "sha512-nrdWApVkP5cksAnDEyy1JD9rKdwOJsEq1B+seWO4vNXmZNcxQQCx4DULLBvKt7AzRUAQiAuw5aQkb9RBaSqdVA==",
"dev": true,
"dependencies": {
"muggle-string": "^0.4.0"
}
},
"node_modules/@volar/typescript": {
"version": "2.2.0-alpha.8",
"resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.2.0-alpha.8.tgz",
"integrity": "sha512-RLbRDI+17CiayHZs9HhSzlH0FhLl/+XK6o2qoiw2o2GGKcyD1aDoY6AcMd44acYncTOrqoTNoY6LuCiRyiJiGg==",
"version": "2.2.0-alpha.10",
"resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.2.0-alpha.10.tgz",
"integrity": "sha512-GCa0vTVVdA9ULUsu2Rx7jwsIuyZQPvPVT9o3NrANTbYv+523Ao1gv3glC5vzNSDPM6bUl37r94HbCj7KINQr+g==",
"dev": true,
"dependencies": {
"@volar/language-core": "2.2.0-alpha.8",
"@volar/language-core": "2.2.0-alpha.10",
"path-browserify": "^1.0.1"
}
},
"node_modules/@vue/compiler-core": {
"version": "3.4.21",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.21.tgz",
"integrity": "sha512-MjXawxZf2SbZszLPYxaFCjxfibYrzr3eYbKxwpLR9EQN+oaziSu3qKVbwBERj1IFIB8OLUewxB5m/BFzi613og==",
"version": "3.4.23",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.23.tgz",
"integrity": "sha512-HAFmuVEwNqNdmk+w4VCQ2pkLk1Vw4XYiiyxEp3z/xvl14aLTUBw2OfVH3vBcx+FtGsynQLkkhK410Nah1N2yyQ==",
"dependencies": {
"@babel/parser": "^7.23.9",
"@vue/shared": "3.4.21",
"@babel/parser": "^7.24.1",
"@vue/shared": "3.4.23",
"entities": "^4.5.0",
"estree-walker": "^2.0.2",
"source-map-js": "^1.0.2"
"source-map-js": "^1.2.0"
}
},
"node_modules/@vue/compiler-dom": {
"version": "3.4.21",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.21.tgz",
"integrity": "sha512-IZC6FKowtT1sl0CR5DpXSiEB5ayw75oT2bma1BEhV7RRR1+cfwLrxc2Z8Zq/RGFzJ8w5r9QtCOvTjQgdn0IKmA==",
"version": "3.4.23",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.23.tgz",
"integrity": "sha512-t0b9WSTnCRrzsBGrDd1LNR5HGzYTr7LX3z6nNBG+KGvZLqrT0mY6NsMzOqlVMBKKXKVuusbbB5aOOFgTY+senw==",
"dependencies": {
"@vue/compiler-core": "3.4.21",
"@vue/shared": "3.4.21"
"@vue/compiler-core": "3.4.23",
"@vue/shared": "3.4.23"
}
},
"node_modules/@vue/compiler-sfc": {
"version": "3.4.21",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.21.tgz",
"integrity": "sha512-me7epoTxYlY+2CUM7hy9PCDdpMPfIwrOvAXud2Upk10g4YLv9UBW7kL798TvMeDhPthkZ0CONNrK2GoeI1ODiQ==",
"version": "3.4.23",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.23.tgz",
"integrity": "sha512-fSDTKTfzaRX1kNAUiaj8JB4AokikzStWgHooMhaxyjZerw624L+IAP/fvI4ZwMpwIh8f08PVzEnu4rg8/Npssw==",
"dependencies": {
"@babel/parser": "^7.23.9",
"@vue/compiler-core": "3.4.21",
"@vue/compiler-dom": "3.4.21",
"@vue/compiler-ssr": "3.4.21",
"@vue/shared": "3.4.21",
"@babel/parser": "^7.24.1",
"@vue/compiler-core": "3.4.23",
"@vue/compiler-dom": "3.4.23",
"@vue/compiler-ssr": "3.4.23",
"@vue/shared": "3.4.23",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.7",
"postcss": "^8.4.35",
"source-map-js": "^1.0.2"
"magic-string": "^0.30.8",
"postcss": "^8.4.38",
"source-map-js": "^1.2.0"
}
},
"node_modules/@vue/compiler-ssr": {
"version": "3.4.21",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.21.tgz",
"integrity": "sha512-M5+9nI2lPpAsgXOGQobnIueVqc9sisBFexh5yMIMRAPYLa7+5wEJs8iqOZc1WAa9WQbx9GR2twgznU8LTIiZ4Q==",
"version": "3.4.23",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.23.tgz",
"integrity": "sha512-hb6Uj2cYs+tfqz71Wj6h3E5t6OKvb4MVcM2Nl5i/z1nv1gjEhw+zYaNOV+Xwn+SSN/VZM0DgANw5TuJfxfezPg==",
"dependencies": {
"@vue/compiler-dom": "3.4.21",
"@vue/shared": "3.4.21"
"@vue/compiler-dom": "3.4.23",
"@vue/shared": "3.4.23"
}
},
"node_modules/@vue/devtools-api": {
@ -1660,12 +1687,12 @@
}
},
"node_modules/@vue/language-core": {
"version": "2.0.13",
"resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.0.13.tgz",
"integrity": "sha512-oQgM+BM66SU5GKtUMLQSQN0bxHFkFpLSSAiY87wVziPaiNQZuKVDt/3yA7GB9PiQw0y/bTNL0bOc0jM/siYjKg==",
"version": "2.0.14",
"resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.0.14.tgz",
"integrity": "sha512-3q8mHSNcGTR7sfp2X6jZdcb4yt8AjBXAfKk0qkZIh7GAJxOnoZ10h5HToZglw4ToFvAnq+xu/Z2FFbglh9Icag==",
"dev": true,
"dependencies": {
"@volar/language-core": "2.2.0-alpha.8",
"@volar/language-core": "2.2.0-alpha.10",
"@vue/compiler-dom": "^3.4.0",
"@vue/shared": "^3.4.0",
"computeds": "^0.0.1",
@ -1683,48 +1710,48 @@
}
},
"node_modules/@vue/reactivity": {
"version": "3.4.21",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.21.tgz",
"integrity": "sha512-UhenImdc0L0/4ahGCyEzc/pZNwVgcglGy9HVzJ1Bq2Mm9qXOpP8RyNTjookw/gOCUlXSEtuZ2fUg5nrHcoqJcw==",
"version": "3.4.23",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.23.tgz",
"integrity": "sha512-GlXR9PL+23fQ3IqnbSQ8OQKLodjqCyoCrmdLKZk3BP7jN6prWheAfU7a3mrltewTkoBm+N7qMEb372VHIkQRMQ==",
"dependencies": {
"@vue/shared": "3.4.21"
"@vue/shared": "3.4.23"
}
},
"node_modules/@vue/runtime-core": {
"version": "3.4.21",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.21.tgz",
"integrity": "sha512-pQthsuYzE1XcGZznTKn73G0s14eCJcjaLvp3/DKeYWoFacD9glJoqlNBxt3W2c5S40t6CCcpPf+jG01N3ULyrA==",
"version": "3.4.23",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.23.tgz",
"integrity": "sha512-FeQ9MZEXoFzFkFiw9MQQ/FWs3srvrP+SjDKSeRIiQHIhtkzoj0X4rWQlRNHbGuSwLra6pMyjAttwixNMjc/xLw==",
"dependencies": {
"@vue/reactivity": "3.4.21",
"@vue/shared": "3.4.21"
"@vue/reactivity": "3.4.23",
"@vue/shared": "3.4.23"
}
},
"node_modules/@vue/runtime-dom": {
"version": "3.4.21",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.21.tgz",
"integrity": "sha512-gvf+C9cFpevsQxbkRBS1NpU8CqxKw0ebqMvLwcGQrNpx6gqRDodqKqA+A2VZZpQ9RpK2f9yfg8VbW/EpdFUOJw==",
"version": "3.4.23",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.23.tgz",
"integrity": "sha512-RXJFwwykZWBkMiTPSLEWU3kgVLNAfActBfWFlZd0y79FTUxexogd0PLG4HH2LfOktjRxV47Nulygh0JFXe5f9A==",
"dependencies": {
"@vue/runtime-core": "3.4.21",
"@vue/shared": "3.4.21",
"@vue/runtime-core": "3.4.23",
"@vue/shared": "3.4.23",
"csstype": "^3.1.3"
}
},
"node_modules/@vue/server-renderer": {
"version": "3.4.21",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.21.tgz",
"integrity": "sha512-aV1gXyKSN6Rz+6kZ6kr5+Ll14YzmIbeuWe7ryJl5muJ4uwSwY/aStXTixx76TwkZFJLm1aAlA/HSWEJ4EyiMkg==",
"version": "3.4.23",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.23.tgz",
"integrity": "sha512-LDwGHtnIzvKFNS8dPJ1SSU5Gvm36p2ck8wCZc52fc3k/IfjKcwCyrWEf0Yag/2wTFUBXrqizfhK9c/mC367dXQ==",
"dependencies": {
"@vue/compiler-ssr": "3.4.21",
"@vue/shared": "3.4.21"
"@vue/compiler-ssr": "3.4.23",
"@vue/shared": "3.4.23"
},
"peerDependencies": {
"vue": "3.4.21"
"vue": "3.4.23"
}
},
"node_modules/@vue/shared": {
"version": "3.4.21",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.21.tgz",
"integrity": "sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g=="
"version": "3.4.23",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.23.tgz",
"integrity": "sha512-wBQ0gvf+SMwsCQOyusNw/GoXPV47WGd1xB5A1Pgzy0sQ3Bi5r5xm3n+92y3gCnB3MWqnRDdvfkRGxhKtbBRNgg=="
},
"node_modules/@vuelidate/core": {
"version": "2.0.3",
@ -1811,9 +1838,9 @@
}
},
"node_modules/@vuepic/vue-datepicker": {
"version": "8.4.0",
"resolved": "https://registry.npmjs.org/@vuepic/vue-datepicker/-/vue-datepicker-8.4.0.tgz",
"integrity": "sha512-Twgvqwd5GrQf3JT2DvAQ/Ku0+sM51zsH1OkQKoRwYqJyF+EugItS8I0CveYmcI3Gbu92RZ9C3DMutvkaiuDzAQ==",
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/@vuepic/vue-datepicker/-/vue-datepicker-8.5.0.tgz",
"integrity": "sha512-p8CHPJYJ1nQgrKzVBaDi1ZO9G9syuvOacPDNMF4uViHsXGdUyGLZbgrvvcmDTd0xYtyCUswiH6S27gb1E7qQ2Q==",
"dependencies": {
"date-fns": "^3.6.0"
},
@ -3705,14 +3732,11 @@
}
},
"node_modules/magic-string": {
"version": "0.30.9",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.9.tgz",
"integrity": "sha512-S1+hd+dIrC8EZqKyT9DstTH/0Z+f76kmmvZnkfQVmOpDEF9iVgdYif3Q/pIWHmCoo59bQVGW0kVL3e2nl+9+Sw==",
"version": "0.30.10",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz",
"integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15"
},
"engines": {
"node": ">=12"
}
},
"node_modules/make-dir": {
@ -3933,9 +3957,9 @@
}
},
"node_modules/nwsapi": {
"version": "2.2.7",
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz",
"integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==",
"version": "2.2.9",
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.9.tgz",
"integrity": "sha512-2f3F0SEEer8bBu0dsNCFF50N0cTThV1nWFYcEYFZttdW0lDAoybv9cQoK7X7/68Z89S7FoRrVjP1LPX4XRf9vg==",
"dev": true
},
"node_modules/oas-kit-common": {
@ -4567,9 +4591,9 @@
"integrity": "sha512-OlwfYEgA2RdboZohpldlvJ1xngOins5d7ejqnIBWr9KaMxsnBqotpptRXTyfNRLnFpqzX6sTDt+X+a+6udnU8g=="
},
"node_modules/rollup": {
"version": "4.14.3",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.14.3.tgz",
"integrity": "sha512-ag5tTQKYsj1bhrFC9+OEWqb5O6VYgtQDO9hPDBMmIbePwhfSr+ExlcU741t8Dhw5DkPCQf6noz0jb36D6W9/hw==",
"version": "4.16.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.16.1.tgz",
"integrity": "sha512-5CaD3MPDlPKfhqzRvWXK96G6ELJfPZNb3LHiZxTHgDdC6jvwfGz2E8nY+9g1ONk4ttHsK1WaFP19Js4PSr1E3g==",
"dev": true,
"dependencies": {
"@types/estree": "1.0.5"
@ -4582,22 +4606,22 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.14.3",
"@rollup/rollup-android-arm64": "4.14.3",
"@rollup/rollup-darwin-arm64": "4.14.3",
"@rollup/rollup-darwin-x64": "4.14.3",
"@rollup/rollup-linux-arm-gnueabihf": "4.14.3",
"@rollup/rollup-linux-arm-musleabihf": "4.14.3",
"@rollup/rollup-linux-arm64-gnu": "4.14.3",
"@rollup/rollup-linux-arm64-musl": "4.14.3",
"@rollup/rollup-linux-powerpc64le-gnu": "4.14.3",
"@rollup/rollup-linux-riscv64-gnu": "4.14.3",
"@rollup/rollup-linux-s390x-gnu": "4.14.3",
"@rollup/rollup-linux-x64-gnu": "4.14.3",
"@rollup/rollup-linux-x64-musl": "4.14.3",
"@rollup/rollup-win32-arm64-msvc": "4.14.3",
"@rollup/rollup-win32-ia32-msvc": "4.14.3",
"@rollup/rollup-win32-x64-msvc": "4.14.3",
"@rollup/rollup-android-arm-eabi": "4.16.1",
"@rollup/rollup-android-arm64": "4.16.1",
"@rollup/rollup-darwin-arm64": "4.16.1",
"@rollup/rollup-darwin-x64": "4.16.1",
"@rollup/rollup-linux-arm-gnueabihf": "4.16.1",
"@rollup/rollup-linux-arm-musleabihf": "4.16.1",
"@rollup/rollup-linux-arm64-gnu": "4.16.1",
"@rollup/rollup-linux-arm64-musl": "4.16.1",
"@rollup/rollup-linux-powerpc64le-gnu": "4.16.1",
"@rollup/rollup-linux-riscv64-gnu": "4.16.1",
"@rollup/rollup-linux-s390x-gnu": "4.16.1",
"@rollup/rollup-linux-x64-gnu": "4.16.1",
"@rollup/rollup-linux-x64-musl": "4.16.1",
"@rollup/rollup-win32-arm64-msvc": "4.16.1",
"@rollup/rollup-win32-ia32-msvc": "4.16.1",
"@rollup/rollup-win32-x64-msvc": "4.16.1",
"fsevents": "~2.3.2"
}
},
@ -5324,9 +5348,9 @@
"dev": true
},
"node_modules/vite": {
"version": "5.2.8",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.2.8.tgz",
"integrity": "sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==",
"version": "5.2.10",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.2.10.tgz",
"integrity": "sha512-PAzgUZbP7msvQvqdSD+ErD5qGnSFiGOoWmV5yAKUEI0kdhjbH6nMWVyZQC/hSc4aXwc0oJ9aEdIiF9Oje0JFCw==",
"dev": true,
"dependencies": {
"esbuild": "^0.20.1",
@ -5409,15 +5433,15 @@
}
},
"node_modules/vue": {
"version": "3.4.21",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.4.21.tgz",
"integrity": "sha512-5hjyV/jLEIKD/jYl4cavMcnzKwjMKohureP8ejn3hhEjwhWIhWeuzL2kJAjzl/WyVsgPY56Sy4Z40C3lVshxXA==",
"version": "3.4.23",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.4.23.tgz",
"integrity": "sha512-X1y6yyGJ28LMUBJ0k/qIeKHstGd+BlWQEOT40x3auJFTmpIhpbKLgN7EFsqalnJXq1Km5ybDEsp6BhuWKciUDg==",
"dependencies": {
"@vue/compiler-dom": "3.4.21",
"@vue/compiler-sfc": "3.4.21",
"@vue/runtime-dom": "3.4.21",
"@vue/server-renderer": "3.4.21",
"@vue/shared": "3.4.21"
"@vue/compiler-dom": "3.4.23",
"@vue/compiler-sfc": "3.4.23",
"@vue/runtime-dom": "3.4.23",
"@vue/server-renderer": "3.4.23",
"@vue/shared": "3.4.23"
},
"peerDependencies": {
"typescript": "*"
@ -5513,9 +5537,9 @@
}
},
"node_modules/vue-router": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.3.0.tgz",
"integrity": "sha512-dqUcs8tUeG+ssgWhcPbjHvazML16Oga5w34uCUmsk7i0BcnskoLGwjpa15fqMr2Fa5JgVBrdL2MEgqz6XZ/6IQ==",
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.3.2.tgz",
"integrity": "sha512-hKQJ1vDAZ5LVkKEnHhmm1f9pMiWIBNGF5AwU67PdH7TyXCj/a4hTccuUuYCAMgJK6rO/NVYtQIEN3yL8CECa7Q==",
"dependencies": {
"@vue/devtools-api": "^6.5.1"
},
@ -5537,13 +5561,13 @@
}
},
"node_modules/vue-tsc": {
"version": "2.0.13",
"resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.0.13.tgz",
"integrity": "sha512-a3nL3FvguCWVJUQW/jFrUxdeUtiEkbZoQjidqvMeBK//tuE2w6NWQAbdrEpY2+6nSa4kZoKZp8TZUMtHpjt4mQ==",
"version": "2.0.14",
"resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.0.14.tgz",
"integrity": "sha512-DgAO3U1cnCHOUO7yB35LENbkapeRsBZ7Ugq5hGz/QOHny0+1VQN8eSwSBjYbjLVPfvfw6EY7sNPjbuHHUhckcg==",
"dev": true,
"dependencies": {
"@volar/typescript": "2.2.0-alpha.8",
"@vue/language-core": "2.0.13",
"@volar/typescript": "2.2.0-alpha.10",
"@vue/language-core": "2.0.14",
"semver": "^7.5.4"
},
"bin": {
@ -5687,9 +5711,9 @@
}
},
"node_modules/wavesurfer.js": {
"version": "7.7.10",
"resolved": "https://registry.npmjs.org/wavesurfer.js/-/wavesurfer.js-7.7.10.tgz",
"integrity": "sha512-fpQ/OiHGkFxD6cJRuU/VQ+iuYM4ipMvMyD5fp0ENPPHYwiZeb01nqXYdXPemK70vAxNDHkVqAsFqLZEchxySJg=="
"version": "7.7.11",
"resolved": "https://registry.npmjs.org/wavesurfer.js/-/wavesurfer.js-7.7.11.tgz",
"integrity": "sha512-27otJlDBcUSbMh/YkxEZN0yXTHINQNYSOl5aJplYlIaHw2u3BrABUzOmUZV3dKIy8udOLJXS/67y5pOIzgAg+w=="
},
"node_modules/web-streams-polyfill": {
"version": "3.3.3",

View File

@ -11,6 +11,7 @@
},
"dependencies": {
"@codemirror/lang-css": "^6.0.1",
"@codemirror/lang-html": "^6.4.9",
"@codemirror/lang-javascript": "^6.1.2",
"@flowjs/flow.js": "^2.14.1",
"@fullcalendar/bootstrap5": "^6.1.8",

View File

@ -90,6 +90,27 @@
</template>
</form-group-field>
<form-group-field
v-if="enableAdvancedFeatures"
id="edit_form_backend_dj_port_secondary"
class="col-md-6"
:field="v$.backend_config.dj_port_secondary"
input-type="number"
:input-attrs="{ min: '0' }"
advanced
:label="$gettext('Customize DJ/Streamer Port Secondary')"
>
<template #description>
{{
$gettext('No other program can be using this port. Leave blank to automatically assign a port.')
}}
<br>
{{
$gettext('Note: the port after this one will automatically be used for legacy connections.')
}}
</template>
</form-group-field>
<form-group-field
id="edit_form_backend_dj_buffer"
class="col-md-6"

View File

@ -13,6 +13,7 @@ import {useVModel} from "@vueuse/core";
import {computed} from "vue";
import {css} from "@codemirror/lang-css";
import {javascript} from "@codemirror/lang-javascript";
import {html} from "@codemirror/lang-html";
import {liquidsoap} from "codemirror-lang-liquidsoap";
import useTheme from "~/functions/theme";
@ -31,6 +32,8 @@ const lang = computed(() => {
return css();
case 'javascript':
return javascript();
case 'html':
return html();
case 'liquidsoap':
return liquidsoap();
default:

View File

@ -205,6 +205,20 @@ const fields: DataTableField[] = [
sortable: true,
selectable: true
},
{
key: 'season_number',
label: $gettext('Season Number'),
visible: false,
sortable: true,
selectable: true
},
{
key: 'episode_number',
label: $gettext('Episode Number'),
visible: false,
sortable: true,
selectable: true
},
{
key: 'actions',
label: $gettext('Actions'),

View File

@ -36,7 +36,7 @@
<h5 class="m-0">
{{ item.title }}
</h5>
<div v-if="item.is_published">
<div v-if="item.is_published && item.is_enabled">
<a
:href="item.links.public_episodes"
target="_blank"
@ -59,6 +59,12 @@
>
{{ $gettext('Unpublished') }}
</span>
<span
v-if="!item.is_enabled"
class="badge text-bg-danger"
>
{{ $gettext('Disabled') }}
</span>
</div>
</template>
<template #cell(actions)="{item}">

View File

@ -53,6 +53,28 @@
:label="$gettext('Contains explicit content')"
:description="$gettext('Indicates the presence of explicit content (explicit language or adult content). Apple Podcasts displays an Explicit parental advisory graphic for your episode if turned on. Episodes containing explicit material aren\'t available in some Apple Podcasts territories.')"
/>
<form-group-field
id="form_edit_season_number"
class="col-md-6"
:field="v$.season_number"
input-type="number"
:input-attrs="{ step: '1' }"
:label="$gettext('Season Number')"
:description="$gettext('Optionally list this episode as part of a season in some podcast aggregators.')"
clearable
/>
<form-group-field
id="form_edit_episode_number"
class="col-md-6"
:field="v$.episode_number"
input-type="number"
:input-attrs="{ step: '1' }"
:label="$gettext('Episode Number')"
:description="$gettext('Optionally set a specific episode number in some podcast aggregators.')"
clearable
/>
</div>
</tab>
</template>
@ -83,6 +105,8 @@ const {v$, tabClass} = useVuelidateOnFormTab(
publish_date: {},
publish_time: {},
explicit: {},
season_number: {},
episode_number: {}
},
form,
{
@ -91,7 +115,9 @@ const {v$, tabClass} = useVuelidateOnFormTab(
description: '',
publish_date: '',
publish_time: '',
explicit: false
explicit: false,
season_number: null,
episode_number: null
}
);
</script>

View File

@ -10,13 +10,13 @@
>
<tabs>
<podcast-form-basic-info
:form="form"
v-model:form="form"
:categories-options="categoriesOptions"
:language-options="languageOptions"
/>
<podcast-form-source
:form="form"
v-model:form="form"
/>
<podcast-common-artwork
@ -24,6 +24,10 @@
:artwork-src="record.links.art"
:new-art-url="newArtUrl"
/>
<podcast-form-branding
v-model:form="form"
/>
</tabs>
</modal-form>
</template>
@ -31,6 +35,7 @@
<script setup lang="ts">
import PodcastFormBasicInfo from './PodcastForm/BasicInfo.vue';
import PodcastFormSource from './PodcastForm/Source.vue';
import PodcastFormBranding from './PodcastForm/Branding.vue';
import PodcastCommonArtwork from './Common/Artwork.vue';
import mergeExisting from "~/functions/mergeExisting";
import {baseEditModalProps, ModalFormTemplateRef, useBaseEditModal} from "~/functions/useBaseEditModal";
@ -99,8 +104,11 @@ const {
(row) => row.category
);
record.value = data;
formRef.value = mergeExisting(formRef.value, data);
console.log(formRef.value);
},
},
);

View File

@ -63,6 +63,14 @@
:label="$gettext('Categories')"
:description="$gettext('Select the category/categories that best reflects the content of your podcast.')"
/>
<form-group-checkbox
id="edit_form_is_enabled"
class="col-md-12"
:field="v$.is_enabled"
:label="$gettext('Enable on Public Pages')"
:description="$gettext('If disabled, the station will not be visible on public-facing pages or APIs.')"
/>
</div>
</tab>
</template>
@ -74,6 +82,7 @@ import {useVModel} from "@vueuse/core";
import {useVuelidateOnFormTab} from "~/functions/useVuelidateOnFormTab";
import {required} from "@vuelidate/validators";
import Tab from "~/components/Common/Tab.vue";
import FormGroupCheckbox from "~/components/Form/FormGroupCheckbox.vue";
const props = defineProps({
form: {
@ -102,6 +111,7 @@ const {v$, tabClass} = useVuelidateOnFormTab(
author: {},
email: {},
categories: {required},
is_enabled: {},
},
form,
{
@ -112,6 +122,7 @@ const {v$, tabClass} = useVuelidateOnFormTab(
author: '',
email: '',
categories: [],
is_enabled: true
}
);
</script>

View File

@ -0,0 +1,55 @@
<template>
<tab
:label="$gettext('Branding')"
:item-header-class="tabClass"
>
<div class="row g-3">
<form-group-field
id="edit_form_public_custom_html"
class="col-md-12"
:field="v$.branding_config.public_custom_html"
:label="$gettext('Custom HTML for Public Pages')"
>
<template #default="slotProps">
<codemirror-textarea
:id="slotProps.id"
v-model="slotProps.field.$model"
mode="html"
/>
</template>
</form-group-field>
</div>
</tab>
</template>
<script setup lang="ts">
import {useVModel} from "@vueuse/core";
import {useVuelidateOnFormTab} from "~/functions/useVuelidateOnFormTab";
import Tab from "~/components/Common/Tab.vue";
import CodemirrorTextarea from "~/components/Common/CodemirrorTextarea.vue";
import FormGroupField from "~/components/Form/FormGroupField.vue";
const props = defineProps({
form: {
type: Object,
required: true
}
});
const emit = defineEmits(['update:form']);
const form = useVModel(props, 'form', emit);
const {v$, tabClass} = useVuelidateOnFormTab(
{
branding_config: {
public_custom_html: {}
},
},
form,
{
branding_config: {
public_custom_html: ''
}
}
);
</script>

View File

@ -611,6 +611,8 @@ export type ApiPodcast = HasLinks & {
link?: string | null;
description?: string;
description_short?: string;
/** An array containing podcast-specific branding configuration */
branding_config?: any[];
language?: string;
language_name?: string;
author?: string;
@ -633,12 +635,15 @@ export interface ApiPodcastCategory {
export type ApiPodcastEpisode = HasLinks & {
id?: string;
title?: string;
link?: string | null;
description?: string;
description_short?: string;
explicit?: boolean;
season_number?: number | null;
episode_number?: number | null;
created_at?: number;
publish_at?: number;
is_published?: boolean;
publish_at?: number | null;
has_media?: boolean;
playlist_media_id?: string | null;
playlist_media?: ApiSong | null;
@ -1501,6 +1506,8 @@ export type StationPlaylist = HasAutoIncrementId & {
avoid_duplicates?: boolean;
/** StationSchedule> */
schedule_items?: any[];
/** Podcast> */
podcasts?: any[];
};
export type StationSchedule = HasAutoIncrementId & {

View File

@ -35,6 +35,7 @@ final class ListPodcastsAction implements SingleActionInterface
->from(Podcast::class, 'p')
->leftJoin('p.categories', 'pc')
->where('p.storage_location = :storageLocation')
->andWhere('p.is_enabled = 1')
->setParameter('storageLocation', $station->getPodcastsStorageLocation())
->andWhere('p.id IN (:podcastIds)')
->setParameter('podcastIds', $this->podcastRepo->getPodcastIdsWithPublishedEpisodes($station))

View File

@ -7,36 +7,20 @@ namespace App\Controller\Frontend\PublicPages;
use App\Controller\SingleActionInterface;
use App\Entity\ApiGenerator\PodcastApiGenerator;
use App\Entity\ApiGenerator\PodcastEpisodeApiGenerator;
use App\Entity\Podcast;
use App\Entity\PodcastCategory;
use App\Entity\PodcastEpisode;
use App\Exception\NotFoundException;
use App\Http\Response;
use App\Http\ServerRequest;
use App\Rss\PodcastNamespaceWriter;
use DateTime;
use MarcW\RssWriter\Extension\Atom\AtomLink;
use MarcW\RssWriter\Extension\Atom\AtomWriter;
use MarcW\RssWriter\Extension\Core\Category as RssCategory;
use MarcW\RssWriter\Extension\Core\Channel as RssChannel;
use MarcW\RssWriter\Extension\Core\CoreWriter;
use MarcW\RssWriter\Extension\Core\Enclosure as RssEnclosure;
use MarcW\RssWriter\Extension\Core\Guid as RssGuid;
use MarcW\RssWriter\Extension\Core\Image as RssImage;
use MarcW\RssWriter\Extension\Core\Item as RssItem;
use MarcW\RssWriter\Extension\Itunes\ItunesChannel;
use MarcW\RssWriter\Extension\Itunes\ItunesItem;
use MarcW\RssWriter\Extension\Itunes\ItunesOwner;
use MarcW\RssWriter\Extension\Itunes\ItunesWriter;
use MarcW\RssWriter\Extension\Slash\Slash;
use MarcW\RssWriter\Extension\Slash\SlashWriter;
use MarcW\RssWriter\Extension\Sy\Sy;
use MarcW\RssWriter\Extension\Sy\SyWriter;
use MarcW\RssWriter\RssWriter;
use App\Xml\Writer;
use Carbon\CarbonImmutable;
use Psr\Http\Message\ResponseInterface;
use Ramsey\Uuid\Uuid;
final class PodcastFeedAction implements SingleActionInterface
{
public const string PODCAST_NAMESPACE = 'ead4c236-bf58-58c6-a2c6-a6b28d128cb6';
public function __construct(
private readonly PodcastApiGenerator $podcastApiGenerator,
private readonly PodcastEpisodeApiGenerator $episodeApiGenerator
@ -55,40 +39,78 @@ final class PodcastFeedAction implements SingleActionInterface
$podcast = $request->getPodcast();
$channel = new RssChannel();
$channel->setTtl(5);
$channel->setLastBuildDate(new DateTime());
// Fetch podcast API feed.
$podcastApi = $this->podcastApiGenerator->__invoke($podcast, $request);
$channel->setTitle($podcastApi->title);
$channel->setDescription($podcastApi->description);
$channel->setLink($podcastApi->link ?? $podcastApi->links['self']);
$channel->setLanguage($podcastApi->language);
$now = CarbonImmutable::now($station->getTimezoneObject());
$channel->setCategories(
$podcast->getCategories()->map(
$rss = [
'@xmlns:itunes' => 'http://www.itunes.com/dtds/podcast-1.0.dtd',
'@xmlns:sy' => 'http://purl.org/rss/1.0/modules/syndication/',
'@xmlns:slash' => 'http://purl.org/rss/1.0/modules/slash/',
'@xmlns:atom' => 'http://www.w3.org/2005/Atom',
'@xmlns:podcast' => 'https://podcastindex.org/namespace/1.0',
'@version' => '2.0',
];
$channel = [
'title' => $podcastApi->title,
'link' => $podcastApi->link ?? $podcastApi->links['public_episodes'],
'description' => $podcastApi->description,
'language' => $podcastApi->language,
'lastBuildDate' => $now->toRssString(),
'category' => $podcast->getCategories()->map(
function (PodcastCategory $podcastCategory) {
$rssCategory = new RssCategory();
if (null === $podcastCategory->getSubTitle()) {
$rssCategory->setTitle($podcastCategory->getTitle());
} else {
$rssCategory->setTitle($podcastCategory->getSubTitle());
}
return $rssCategory;
return (null === $podcastCategory->getSubTitle())
? $podcastCategory->getTitle()
: $podcastCategory->getSubTitle();
}
)->getValues()
);
)->getValues(),
'ttl' => 5,
'image' => [
'url' => $podcastApi->art,
'title' => $podcastApi->title,
],
'itunes:author' => $podcastApi->author,
'itunes:owner' => [],
'itunes:image' => [
'@href' => $podcastApi->art,
],
'itunes:explicit' => 'false',
'itunes:category' => $podcast->getCategories()->map(
function (PodcastCategory $podcastCategory) {
return (null === $podcastCategory->getSubTitle())
? [
'@text' => $podcastCategory->getTitle(),
] : [
'@text' => $podcastCategory->getTitle(),
'itunes:category' => [
'@text' => $podcastCategory->getSubTitle(),
],
];
}
)->getValues(),
'atom:link' => [
'@rel' => 'self',
'@type' => 'application/rss+xml',
'@href' => (string)$request->getUri(),
],
'podcast:guid' => $this->buildPodcastGuid($podcastApi->links['public_feed']),
'item' => [],
];
$rssImage = new RssImage();
$rssImage->setTitle($podcastApi->title);
$rssImage->setUrl($podcastApi->art);
if (null !== $podcastApi->link) {
$rssImage->setLink($podcastApi->link);
$channel['image']['link'] = $podcastApi->link;
}
$channel->setImage($rssImage);
if (empty($podcastApi->author) && empty($podcastApi->email)) {
unset($channel['itunes:owner']);
} else {
$channel['itunes:owner'] = [
'itunes:name' => $podcastApi->author,
'itunes:email' => $podcastApi->email,
];
}
// Iterate through episodes.
$hasPublishedEpisode = false;
@ -105,53 +127,21 @@ final class PodcastFeedAction implements SingleActionInterface
$hasExplicitEpisode = true;
}
$channel->addItem($this->buildItemForEpisode($episode, $request));
$channel['item'][] = $this->buildItemForEpisode($episode, $request);
}
if (!$hasPublishedEpisode) {
throw NotFoundException::podcast();
}
$itunesChannel = new ItunesChannel();
$itunesChannel->setExplicit($hasExplicitEpisode);
$itunesChannel->setImage($rssImage->getUrl());
$itunesChannel->setCategories(
$podcast->getCategories()->map(
function (PodcastCategory $podcastCategory) {
return (null === $podcastCategory->getSubTitle())
? $podcastCategory->getTitle()
: [
$podcastCategory->getTitle(),
$podcastCategory->getSubTitle(),
];
}
)->getValues()
);
if ($hasExplicitEpisode) {
$channel['itunes:explicit'] = 'true';
}
$itunesChannel->setOwner($this->buildItunesOwner($podcast));
$itunesChannel->setAuthor($podcast->getAuthor());
$channel->addExtension($itunesChannel);
$channel->addExtension(new Sy());
$channel->addExtension(new Slash());
$channel->addExtension(
(new AtomLink())
->setRel('self')
->setHref((string)$request->getUri())
->setType('application/rss+xml')
);
$rssWriter = new RssWriter(null, [
new CoreWriter(),
new ItunesWriter(),
new SyWriter(),
new SlashWriter(),
new AtomWriter(),
new PodcastNamespaceWriter(),
], true);
$rss['channel'] = $channel;
$response->getBody()->write(
$rssWriter->writeChannel($channel)
Writer::toString($rss, 'rss')
);
return $response
@ -159,48 +149,58 @@ final class PodcastFeedAction implements SingleActionInterface
->withHeader('X-Robots-Tag', 'index, nofollow');
}
private function buildItemForEpisode(PodcastEpisode $episode, ServerRequest $request): RssItem
private function buildItemForEpisode(PodcastEpisode $episode, ServerRequest $request): array
{
$station = $request->getStation();
$episodeApi = $this->episodeApiGenerator->__invoke($episode, $request);
$rssItem = new RssItem();
$publishedAt = CarbonImmutable::createFromTimestamp($episodeApi->publish_at, $station->getTimezoneObject());
$rssItem->setGuid((new RssGuid())->setGuid($episodeApi->id));
$rssItem->setTitle($episodeApi->title);
$rssItem->setDescription($episodeApi->description);
$rssItem->setLink($episodeApi->link ?? $episodeApi->links['self']);
$rssItem->setPubDate((new DateTime())->setTimestamp($episode->getPublishAt()));
$rssEnclosure = new RssEnclosure();
$rssEnclosure->setUrl($episodeApi->links['download']);
$item = [
'title' => $episodeApi->title,
'link' => $episodeApi->link ?? $episodeApi->links['public'],
'description' => $episodeApi->description,
'enclosure' => [
'@url' => $episodeApi->links['download'],
],
'guid' => [
'@isPermaLink' => 'false',
'_' => $episodeApi->id,
],
'pubDate' => $publishedAt->toRssString(),
'itunes:image' => [
'@href' => $episodeApi->art,
],
'itunes:explicit' => $episodeApi->explicit ? 'true' : 'false',
];
$podcastMedia = $episode->getMedia();
if (null !== $podcastMedia) {
$rssEnclosure->setType($podcastMedia->getMimeType());
$rssEnclosure->setLength($podcastMedia->getLength());
$item['enclosure']['@length'] = $podcastMedia->getLength();
$item['enclosure']['@type'] = $podcastMedia->getMimeType();
}
$rssItem->setEnclosure($rssEnclosure);
$rssItem->addExtension(
(new ItunesItem())
->setExplicit($episode->getExplicit())
->setImage($episodeApi->art)
if (null !== $episodeApi->season_number) {
$item['itunes:season'] = (string)$episodeApi->season_number;
}
if (null !== $episodeApi->episode_number) {
$item['itunes:episode'] = (string)$episodeApi->episode_number;
}
return $item;
}
private function buildPodcastGuid(string $uri): string
{
$baseUri = rtrim(
str_replace(['https://', 'http://'], '', $uri),
'/'
);
return $rssItem;
}
private function buildItunesOwner(Podcast $podcast): ?ItunesOwner
{
if (empty($podcast->getAuthor()) && empty($podcast->getEmail())) {
return null;
}
$itunesOwner = new ItunesOwner();
$itunesOwner->setName($podcast->getAuthor());
$itunesOwner->setEmail($podcast->getEmail());
return $itunesOwner;
return (string)Uuid::uuid5(
self::PODCAST_NAMESPACE,
$baseUri
);
}
}

View File

@ -29,9 +29,13 @@ abstract class AbstractStationConfiguration implements JsonSerializable
return $return;
}
public function jsonSerialize(): array
public function jsonSerialize(): array|object
{
return $this->toArray();
$result = $this->toArray();
return (0 !== count($result))
? $result
: (object)[];
}
protected function get(string $key, mixed $default = null): mixed

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace App\Entity\Api;
use App\Entity\Api\Traits\HasLinks;
use App\Entity\PodcastBrandingConfiguration;
use OpenApi\Attributes as OA;
#[OA\Schema(
@ -42,6 +43,16 @@ final class Podcast
#[OA\Property]
public string $description_short;
#[OA\Property]
public bool $is_enabled = true;
#[OA\Property(
description: "An array containing podcast-specific branding configuration",
type: "array",
items: new OA\Items()
)]
public PodcastBrandingConfiguration $branding_config;
#[OA\Property]
public string $language;

View File

@ -33,6 +33,12 @@ final class PodcastEpisode
#[OA\Property]
public bool $explicit = false;
#[OA\Property]
public ?int $season_number = null;
#[OA\Property]
public ?int $episode_number = null;
#[OA\Property]
public int $created_at;

View File

@ -48,6 +48,10 @@ final class PodcastApiGenerator
$return->description = $record->getDescription();
$return->description_short = Strings::truncateText($return->description, 200);
$return->is_enabled = $record->isEnabled();
$return->branding_config = $record->getBrandingConfig();
$return->language = $record->getLanguage();
try {
$locale = $request->getCustomization()->getLocale();

View File

@ -41,6 +41,9 @@ final class PodcastEpisodeApiGenerator
$return->description_short = Strings::truncateText($return->description, 100);
$return->explicit = $record->getExplicit();
$return->season_number = $record->getSeasonNumber();
$return->episode_number = $record->getEpisodeNumber();
$return->created_at = $record->getCreatedAt();
$return->publish_at = $record->getPublishAt();

View File

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace App\Entity\Migration;
use Doctrine\DBAL\Schema\Schema;
final class Version20240421094525 extends AbstractMigration
{
public function getDescription(): string
{
return 'Expand podcast database fields.';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE podcast ADD branding_config JSON DEFAULT NULL');
$this->addSql(
'ALTER TABLE podcast_episode ADD season_number INT DEFAULT NULL, ADD episode_number INT DEFAULT NULL'
);
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE podcast DROP branding_config');
$this->addSql('ALTER TABLE podcast_episode DROP season_number, DROP episode_number');
}
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace App\Entity\Migration;
use Doctrine\DBAL\Schema\Schema;
final class Version20240425151151 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add is_enabled flag for podcasts.';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE podcast ADD is_enabled TINYINT(1) NOT NULL AFTER description');
$this->addSql(<<<'SQL'
UPDATE podcast
SET is_enabled=1
SQL);
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE podcast DROP is_enabled');
}
}

View File

@ -52,6 +52,12 @@ class Podcast implements Interfaces\IdentifiableEntityInterface
#[Assert\NotBlank]
protected string $description;
#[ORM\Column]
protected bool $is_enabled = true;
#[ORM\Column(type: 'json', nullable: true)]
protected ?array $branding_config = null;
#[ORM\Column(length: 2)]
#[Assert\NotBlank]
protected string $language;
@ -147,6 +153,34 @@ class Podcast implements Interfaces\IdentifiableEntityInterface
return $this;
}
public function isEnabled(): bool
{
return $this->is_enabled;
}
public function setIsEnabled(bool $is_enabled): void
{
$this->is_enabled = $is_enabled;
}
public function getBrandingConfig(): PodcastBrandingConfiguration
{
return new PodcastBrandingConfiguration((array)$this->branding_config);
}
public function setBrandingConfig(
PodcastBrandingConfiguration|array $brandingConfig,
bool $forceOverwrite = false
): void {
if (is_array($brandingConfig)) {
$brandingConfig = new PodcastBrandingConfiguration(
$forceOverwrite ? $brandingConfig : array_merge((array)$this->branding_config, $brandingConfig)
);
}
$this->branding_config = $brandingConfig->toArray();
}
public function getLanguage(): string
{
return $this->language;

View File

@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace App\Entity;
use App\Utilities\Types;
class PodcastBrandingConfiguration extends AbstractStationConfiguration
{
public const PUBLIC_CUSTOM_HTML = 'public_custom_html';
public function getPublicCustomHtml(): ?string
{
return Types::stringOrNull($this->get(self::PUBLIC_CUSTOM_HTML), true);
}
public function setPublicCustomHtml(?string $html): void
{
$this->set(self::PUBLIC_CUSTOM_HTML, $html);
}
}

View File

@ -52,6 +52,12 @@ class PodcastEpisode implements IdentifiableEntityInterface
#[ORM\Column]
protected bool $explicit;
#[ORM\Column(nullable: true)]
protected ?int $season_number;
#[ORM\Column(nullable: true)]
protected ?int $episode_number;
#[ORM\Column]
protected int $created_at;
@ -151,6 +157,30 @@ class PodcastEpisode implements IdentifiableEntityInterface
return $this;
}
public function getSeasonNumber(): ?int
{
return $this->season_number;
}
public function setSeasonNumber(?int $season_number): self
{
$this->season_number = $season_number;
return $this;
}
public function getEpisodeNumber(): ?int
{
return $this->episode_number;
}
public function setEpisodeNumber(?int $episode_number): self
{
$this->episode_number = $episode_number;
return $this;
}
public function getCreatedAt(): int
{
return $this->created_at;

View File

@ -39,6 +39,18 @@ class StationBackendConfiguration extends AbstractStationConfiguration
$this->set(self::DJ_PORT, $port);
}
public const DJ_PORT_SECONDARY = 'dj_port_decondary';
public function getDjPortSecondary(): ?int
{
return Types::intOrNull($this->get(self::DJ_PORT_SECONDARY));
}
public function setDjPortSecondary(?int $port): void
{
$this->set(self::DJ_PORT_SECONDARY, $port);
}
public const TELNET_PORT = 'telnet_port';
public function getTelnetPort(): ?int

View File

@ -27,7 +27,8 @@ final class RequirePublishedPodcastEpisodeMiddleware extends AbstractMiddleware
$publishedPodcastIds = $this->podcastRepository->getPodcastIdsWithPublishedEpisodes($station);
$podcast = $request->getPodcast();
if (!in_array($podcast->getIdRequired(), $publishedPodcastIds, true)) {
if (!$podcast->isEnabled() || !in_array($podcast->getIdRequired(), $publishedPodcastIds, true)) {
throw NotFoundException::podcast();
}

View File

@ -59,6 +59,27 @@ final class Liquidsoap extends AbstractLocalAdapter
return $frontendPort + 5;
}
/**
* Returns the port used for a second DJs/Streamers to connect to LiquidSoap for broadcasting.
*
* @param Station $station
*
* @return int The port number to use for this station.
*/
public function getStreamPortSecondary(Station $station): int
{
$djPortSecondary = $station->getBackendConfig()->getDjPortSecondary();
if (null !== $djPortSecondary) {
return $djPortSecondary;
}
// Default to frontend port + 7
$frontendConfig = $station->getFrontendConfig();
$frontendPort = $frontendConfig->getPort() ?? (8000 + (($station->getId() - 1) * 10));
return $frontendPort + 7;
}
/**
* Execute the specified remote command on LiquidSoap via the telnet API.
*

View File

@ -1030,7 +1030,41 @@ final class ConfigWriter implements EventSubscriberInterface
# Live Broadcasting
live = input.harbor({$harborParams})
LIQ
);
// Live Secondary Port
$streamPortSecondary = $this->liquidsoap->getStreamPortSecondary($station);
// Paramètres pour live Secondary
$harborSecondary_params = [
'"' . self::cleanUpString($dj_mount) . '"', // Assurez-vous que $dj_mount est défini correctement
'id = "input_streamer_live_secondary"', // ID unique pour live1
'port = ' . $streamPortSecondary, // Utilisation de getStreamPort1 pour le port
'auth = dj_auth', // Authentification, assurez-vous que 'dj_auth' est correctement défini
'icy = true', // Paramètres ICY
'icy_metadata_charset = "' . $charset . '"', // Charset pour les métadonnées ICY, assurez-vous que $charset est défini
'metadata_charset = "' . $charset . '"', // Charset pour les métadonnées, assurez-vous que $charset est défini
'on_connect = live_connected', // Action à la connexion
'on_disconnect = live_disconnected', // Action à la déconnexion
];
// Ajouter des paramètres de buffer si nécessaire
$djBuffer = $settings->getDjBuffer(); // Assurez-vous que $settings est défini et a une méthode getDjBuffer
if (0 !== $djBuffer) {
$harborSecondary_params[] = 'buffer = ' . self::toFloat($djBuffer);
$harborSecondary_params[] = 'max = ' . self::toFloat(max($djBuffer + 5, 10));
}
// Concaténer les paramètres pour former la chaîne de configuration live1
$harborSecondaryParams = implode(', ', $harborSecondary_params);
// Ajout de la configuration live1 au fichier de configuration LiquidSoap
// Assurez-vous que cette ligne est placée au bon endroit dans votre script pour écrire dans le fichier de configuration
$event->appendBlock("live1 = input.harbor({$harbor1Params})\n");
$event->appendBlock(
<<<LIQ
def insert_missing(m) =
if m == [] then
[("title", "{$liveBroadcastText}"), ("is_live", "true")]
@ -1039,8 +1073,9 @@ final class ConfigWriter implements EventSubscriberInterface
end
end
live = metadata.map(insert_missing, live)
live1 = metadata.map(insert_missing, live1)
radio = fallback(id="live_fallback", track_sensitive=false, replay_metadata=true, [live, radio])
radio = fallback(id="live_fallback", track_sensitive=false, replay_metadata=true, [live, live1, radio])
# Skip non-live track when live DJ goes live.
def check_live() =

View File

@ -289,6 +289,12 @@ final class Configuration
$station->setBackendConfig($backendConfig);
}
$djPortSecondary = $backendConfig->getDjPortSecondary();
if ($force || null === $djPortSecondary) {
$backendConfig->setDjPortSecondary($basePort + 7);
$station->setBackendConfig($backendConfig);
}
$telnetPort = $backendConfig->getTelnetPort();
if ($force || null === $telnetPort) {
$backendConfig->setTelnetPort($basePort + 4);
@ -374,6 +380,11 @@ final class Configuration
$usedPorts[$port] = $stationReference;
$usedPorts[$port + 1] = $stationReference;
}
if (!empty($backendConfig['dj_port_secondary'])) {
$port = (int)$backendConfig['dj_port_secondary'];
$usedPorts[$port] = $stationReference;
$usedPorts[$port + 1] = $stationReference;
}
if (!empty($backendConfig['telnet_port'])) {
$port = (int)$backendConfig['telnet_port'];
$usedPorts[$port] = $stationReference;

View File

@ -292,10 +292,7 @@ final class Icecast extends AbstractFrontend
}
}
$configString = Writer::toString($config, 'icecast');
// Strip the first line (the XML charset)
return substr($configString, strpos($configString, "\n") + 1);
return Writer::toString($config, 'icecast', false);
}
public function getCommand(Station $station): ?string

View File

@ -1,25 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Rss;
use MarcW\RssWriter\WriterRegistererInterface;
/**
* Placeholder class to write the Podcast namespace for PSP-1 compliance.
*/
class PodcastNamespaceWriter implements WriterRegistererInterface
{
public function getRegisteredWriters(): array
{
return [];
}
public function getRegisteredNamespaces(): array
{
return [
'podcast' => 'https://podcastindex.org/namespace/1.0',
];
}
}

View File

@ -32,6 +32,7 @@ final class StationPortCheckerValidator extends ConstraintValidator
$portsToCheck = [
'frontend_config_port' => $frontendConfig->getPort(),
'backend_config_dj_port' => $backendConfig->getDjPort(),
'backend_config_dj_port_secondary' => $backendConfig->getDjPortSecondary(),
'backend_config_telnet_port' => $backendConfig->getTelnetPort(),
];

View File

@ -8,32 +8,25 @@ declare(strict_types=1);
namespace App\Xml;
use RuntimeException;
use XMLWriter;
final class Writer
{
public static function toString(
array $config,
string $baseElement = 'xml-config'
): string {
return self::processConfig($config, $baseElement);
}
private static function processConfig(
array $config,
string $baseElement = 'xml-config'
string $baseElement = 'xml-config',
bool $includeOpeningTag = true
): string {
$writer = new XMLWriter();
$writer->openMemory();
$writer->setIndent(true);
$writer->setIndentString(str_repeat(' ', 4));
$writer->startDocument('1.0', 'UTF-8');
$writer->startElement($baseElement);
if ($includeOpeningTag) {
$writer->startDocument('1.0', 'UTF-8');
}
// Make sure attributes come first
uksort($config, [self::class, 'attributesFirst']);
$writer->startElement($baseElement);
foreach ($config as $sectionName => $data) {
if (!is_array($data)) {
@ -58,54 +51,50 @@ final class Writer
array $config,
XMLWriter $writer
): void {
$branchType = null;
// Ensure attributes come first.
uksort($config, [self::class, 'attributesFirst']);
$attributes = [];
$innerText = null;
foreach ($config as $key => $value) {
if ($branchType === null) {
if (is_numeric($key)) {
$branchType = 'numeric';
} else {
$writer->startElement($branchName);
$branchType = 'string';
if (str_starts_with((string)$key, '@')) {
$attributes[substr($key, 1)] = (string)$value;
unset($config[$key]);
} else {
if ('_' === $key) {
$innerText = (string)$value;
unset($config[$key]);
}
} elseif ($branchType !== (is_numeric($key) ? 'numeric' : 'string')) {
throw new RuntimeException('Mixing of string and numeric keys is not allowed');
}
}
if ($branchType === 'numeric') {
if (0 !== count($config) && array_is_list($config)) {
foreach ($config as $value) {
if (is_array($value)) {
self::addBranch($branchName, $value, $writer);
} else {
$writer->writeElement($branchName, (string)$value);
}
} else {
}
} else {
$writer->startElement($branchName);
foreach ($attributes as $attrKey => $attrVal) {
$writer->writeAttribute($attrKey, $attrVal);
}
if (null !== $innerText) {
$writer->text($innerText);
}
foreach ($config as $key => $value) {
/** @var string $key */
if (is_array($value)) {
self::addBranch($key, $value, $writer);
} elseif (str_starts_with($key, '@')) {
$writer->writeAttribute(substr($key, 1), (string)$value);
} else {
$writer->writeElement($key, (string)$value);
}
}
}
if ($branchType === 'string') {
$writer->endElement();
}
}
private static function attributesFirst(mixed $a, mixed $b): int
{
if (str_starts_with((string)$a, '@')) {
return -1;
}
if (str_starts_with((string)$b, '@')) {
return 1;
}
return 0;
}
}

View File

@ -5,7 +5,6 @@ declare(strict_types=1);
namespace <namespace>;
use Doctrine\DBAL\Schema\Schema;
use App\Entity\Migrations\AbstractMigration;
final class <className> extends AbstractMigration
{
@ -16,12 +15,12 @@ final class <className> extends AbstractMigration
public function up(Schema $schema): void
{
<up>
<up>
}
public function down(Schema $schema): void
{
<down>
<down>
}
<override>
<override>
}

View File

@ -3944,6 +3944,13 @@ components:
type: string
storage_location_id:
type: integer
source:
type: string
playlist_id:
type: integer
nullable: true
playlist_auto_publish:
type: boolean
title:
type: string
link:
@ -3953,6 +3960,10 @@ components:
type: string
description_short:
type: string
branding_config:
description: 'An array containing podcast-specific branding configuration'
type: array
items: { }
language:
type: string
language_name:
@ -3999,23 +4010,42 @@ components:
type: string
title:
type: string
link:
type: string
nullable: true
description:
type: string
description_short:
type: string
explicit:
type: boolean
season_number:
type: integer
nullable: true
episode_number:
type: integer
nullable: true
created_at:
type: integer
publish_at:
type: integer
is_published:
type: boolean
publish_at:
type: integer
nullable: true
has_media:
type: boolean
playlist_media_id:
type: string
nullable: true
playlist_media:
nullable: true
oneOf:
-
$ref: '#/components/schemas/Api_Song'
media:
$ref: '#/components/schemas/Api_PodcastMedia'
nullable: true
oneOf:
-
$ref: '#/components/schemas/Api_PodcastMedia'
has_custom_art:
type: boolean
art:
@ -5012,6 +5042,10 @@ components:
description: StationSchedule>
type: array
items: { }
podcasts:
description: Podcast>
type: array
items: { }
type: object
StationSchedule:
type: object