From 852b5f3e17ebebc65f1e74cb5e9fd68b4235ee24 Mon Sep 17 00:00:00 2001 From: dece Date: Thu, 11 May 2023 17:03:52 +0200 Subject: [PATCH] =?UTF-8?q?Work=20in=20progress=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 2 +- .gitignore | 1 + .phpactor.json | 11 + composer.json | 7 +- composer.lock | 1055 ++++++++++++++++- config/bundles.php | 1 + config/packages/uid.yaml | 4 + migrations/Version20230506144146.php | 54 + migrations/Version20230508171219.php | 36 + psalm.xml | 16 + public/uploads/files/.gitkeep | 0 public/uploads/images/.gitkeep | 0 src/Constants.php | 12 + src/Controller/Admin/DashboardController.php | 34 + .../Admin/EpisodeCrudController.php | 33 + src/Controller/Admin/Field/FileField.php | 80 ++ .../Admin/PodcastCrudController.php | 34 + src/Controller/FeedController.php | 51 + src/Entity/Episode.php | 109 ++ src/Entity/Podcast.php | 179 +++ src/Repository/EpisodeRepository.php | 66 ++ src/Repository/PodcastRepository.php | 66 ++ src/Service/FeedService.php | 81 ++ symfony.lock | 21 + templates/404.html.twig | 6 + templates/500.html.twig | 6 + templates/admin/field/file.html.twig | 11 + templates/base.html.twig | 7 +- templates/feed/index.html.twig | 10 + templates/feed/podcast.html.twig | 11 + 30 files changed, 1998 insertions(+), 6 deletions(-) create mode 100644 .phpactor.json create mode 100644 config/packages/uid.yaml create mode 100644 migrations/Version20230506144146.php create mode 100644 migrations/Version20230508171219.php create mode 100644 psalm.xml create mode 100644 public/uploads/files/.gitkeep create mode 100644 public/uploads/images/.gitkeep create mode 100644 src/Constants.php create mode 100644 src/Controller/Admin/DashboardController.php create mode 100644 src/Controller/Admin/EpisodeCrudController.php create mode 100644 src/Controller/Admin/Field/FileField.php create mode 100644 src/Controller/Admin/PodcastCrudController.php create mode 100644 src/Controller/FeedController.php create mode 100644 src/Entity/Episode.php create mode 100644 src/Entity/Podcast.php create mode 100644 src/Repository/EpisodeRepository.php create mode 100644 src/Repository/PodcastRepository.php create mode 100644 src/Service/FeedService.php create mode 100644 templates/404.html.twig create mode 100644 templates/500.html.twig create mode 100644 templates/admin/field/file.html.twig create mode 100644 templates/feed/index.html.twig create mode 100644 templates/feed/podcast.html.twig diff --git a/.env b/.env index 881cfad..f7fa301 100644 --- a/.env +++ b/.env @@ -26,7 +26,7 @@ APP_SECRET=c798512b7c48614d5278e8e47bb556ce # DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db" # DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=8.0.32&charset=utf8mb4" # DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=10.11.2-MariaDB&charset=utf8mb4" -DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=15&charset=utf8" +DATABASE_URL="postgresql://lsbc:dev@127.0.0.1:5432/lsbc?serverVersion=15&charset=utf8" ###< doctrine/doctrine-bundle ### ###> symfony/messenger ### diff --git a/.gitignore b/.gitignore index de562d7..d0f6419 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +/public/uploads/ ###> symfony/framework-bundle ### /.env.local diff --git a/.phpactor.json b/.phpactor.json new file mode 100644 index 0000000..980f407 --- /dev/null +++ b/.phpactor.json @@ -0,0 +1,11 @@ +{ + "$schema": "/home/dece/Dev/DevTools/phpactor/phpactor.schema.json", + "language_server_psalm.enabled": true, + "symfony.enabled": true, + "indexer.exclude_patterns": [ + "/vendor/**/Tests/**/*", + "/vendor/**/tests/**/*", + "/var/cache/**/*", + "/vendor/composer/**/*" + ] +} \ No newline at end of file diff --git a/composer.json b/composer.json index d261aac..5633d10 100644 --- a/composer.json +++ b/composer.json @@ -6,11 +6,13 @@ "require": { "php": ">=8.1", "ext-ctype": "*", + "ext-dom": "*", "ext-iconv": "*", "doctrine/annotations": "^2.0", "doctrine/doctrine-bundle": "^2.9", "doctrine/doctrine-migrations-bundle": "^3.2", "doctrine/orm": "^2.15", + "easycorp/easyadmin-bundle": "^4", "phpdocumentor/reflection-docblock": "^5.3", "phpstan/phpdoc-parser": "^1.20", "sensio/framework-extra-bundle": "^6.1", @@ -96,9 +98,10 @@ "symfony/browser-kit": "6.2.*", "symfony/css-selector": "6.2.*", "symfony/debug-bundle": "6.2.*", - "symfony/maker-bundle": "^1.0", + "symfony/maker-bundle": "^1.48", "symfony/phpunit-bridge": "^6.2", "symfony/stopwatch": "6.2.*", - "symfony/web-profiler-bundle": "6.2.*" + "symfony/web-profiler-bundle": "6.2.*", + "vimeo/psalm": "^5.11" } } diff --git a/composer.lock b/composer.lock index 1de1658..763ab06 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "63fd32ecec740d3c9c7a271ee1d28bd3", + "content-hash": "1ba2d85d89b2e99700ce3aefe7517597", "packages": [ { "name": "doctrine/annotations", @@ -1391,6 +1391,99 @@ }, "time": "2022-05-23T21:33:49+00:00" }, + { + "name": "easycorp/easyadmin-bundle", + "version": "v4.6.5", + "source": { + "type": "git", + "url": "https://github.com/EasyCorp/EasyAdminBundle.git", + "reference": "3cdf4c2767e16d101cad33b32223052c1fe512a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/EasyCorp/EasyAdminBundle/zipball/3cdf4c2767e16d101cad33b32223052c1fe512a8", + "reference": "3cdf4c2767e16d101cad33b32223052c1fe512a8", + "shasum": "" + }, + "require": { + "doctrine/doctrine-bundle": "^2.5", + "doctrine/orm": "^2.10", + "ext-json": "*", + "php": ">=8.0.2", + "symfony/asset": "^5.4|^6.0", + "symfony/cache": "^5.4|^6.0", + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/deprecation-contracts": "^3.0", + "symfony/doctrine-bridge": "^5.4|^6.0", + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/filesystem": "^5.4|^6.0", + "symfony/form": "^5.4|^6.0", + "symfony/framework-bundle": "^5.4|^6.0", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/intl": "^5.4|^6.0", + "symfony/property-access": "^5.4|^6.0", + "symfony/security-bundle": "^5.4|^6.0", + "symfony/string": "^5.4|^6.0", + "symfony/translation": "^5.4|^6.0", + "symfony/twig-bundle": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "symfony/validator": "^5.4|^6.0" + }, + "require-dev": { + "doctrine/doctrine-fixtures-bundle": "^3.4", + "phpstan/extension-installer": "^1.2", + "phpstan/phpstan": "^1.9", + "phpstan/phpstan-phpunit": "^1.2", + "phpstan/phpstan-strict-rules": "^1.4", + "phpstan/phpstan-symfony": "^1.2", + "psr/log": "^1.0", + "symfony/browser-kit": "^5.4|^6.0", + "symfony/css-selector": "^5.4|^6.0", + "symfony/dom-crawler": "^5.4|^6.0", + "symfony/phpunit-bridge": "^5.4|^6.0" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "EasyCorp\\Bundle\\EasyAdminBundle\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Project Contributors", + "homepage": "https://github.com/EasyCorp/EasyAdminBundle/graphs/contributors" + } + ], + "description": "Admin generator for Symfony applications", + "homepage": "https://github.com/EasyCorp/EasyAdminBundle", + "keywords": [ + "admin", + "backend", + "generator" + ], + "support": { + "issues": "https://github.com/EasyCorp/EasyAdminBundle/issues", + "source": "https://github.com/EasyCorp/EasyAdminBundle/tree/v4.6.5" + }, + "funding": [ + { + "url": "https://github.com/javiereguiluz", + "type": "github" + } + ], + "time": "2023-05-04T18:22:06+00:00" + }, { "name": "egulias/email-validator", "version": "4.0.1", @@ -5175,6 +5268,88 @@ ], "time": "2022-11-03T14:55:06+00:00" }, + { + "name": "symfony/polyfill-uuid", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-uuid.git", + "reference": "f3cf1a645c2734236ed1e2e671e273eeb3586166" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/f3cf1a645c2734236ed1e2e671e273eeb3586166", + "reference": "f3cf1a645c2734236ed1e2e671e273eeb3586166", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-uuid": "*" + }, + "suggest": { + "ext-uuid": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Uuid\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for uuid functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-03T14:55:06+00:00" + }, { "name": "symfony/process", "version": "v6.2.10", @@ -6641,6 +6816,80 @@ ], "time": "2023-02-14T08:44:56+00:00" }, + { + "name": "symfony/uid", + "version": "v6.2.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/uid.git", + "reference": "d30c72a63897cfa043e1de4d4dd2ffa9ecefcdc0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/uid/zipball/d30c72a63897cfa043e1de4d4dd2ffa9ecefcdc0", + "reference": "d30c72a63897cfa043e1de4d4dd2ffa9ecefcdc0", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-uuid": "^1.15" + }, + "require-dev": { + "symfony/console": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Uid\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to generate and represent UIDs", + "homepage": "https://symfony.com", + "keywords": [ + "UID", + "ulid", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/uid/tree/v6.2.7" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-02-14T08:44:56+00:00" + }, { "name": "symfony/validator", "version": "v6.2.10", @@ -7276,6 +7525,589 @@ } ], "packages-dev": [ + { + "name": "amphp/amp", + "version": "v2.6.2", + "source": { + "type": "git", + "url": "https://github.com/amphp/amp.git", + "reference": "9d5100cebffa729aaffecd3ad25dc5aeea4f13bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/amp/zipball/9d5100cebffa729aaffecd3ad25dc5aeea4f13bb", + "reference": "9d5100cebffa729aaffecd3ad25dc5aeea4f13bb", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "amphp/phpunit-util": "^1", + "ext-json": "*", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^7 | ^8 | ^9", + "psalm/phar": "^3.11@dev", + "react/promise": "^2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "files": [ + "lib/functions.php", + "lib/Internal/functions.php" + ], + "psr-4": { + "Amp\\": "lib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Bob Weinand", + "email": "bobwei9@hotmail.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A non-blocking concurrency framework for PHP applications.", + "homepage": "https://amphp.org/amp", + "keywords": [ + "async", + "asynchronous", + "awaitable", + "concurrency", + "event", + "event-loop", + "future", + "non-blocking", + "promise" + ], + "support": { + "irc": "irc://irc.freenode.org/amphp", + "issues": "https://github.com/amphp/amp/issues", + "source": "https://github.com/amphp/amp/tree/v2.6.2" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2022-02-20T17:52:18+00:00" + }, + { + "name": "amphp/byte-stream", + "version": "v1.8.1", + "source": { + "type": "git", + "url": "https://github.com/amphp/byte-stream.git", + "reference": "acbd8002b3536485c997c4e019206b3f10ca15bd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/byte-stream/zipball/acbd8002b3536485c997c4e019206b3f10ca15bd", + "reference": "acbd8002b3536485c997c4e019206b3f10ca15bd", + "shasum": "" + }, + "require": { + "amphp/amp": "^2", + "php": ">=7.1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "amphp/phpunit-util": "^1.4", + "friendsofphp/php-cs-fixer": "^2.3", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^6 || ^7 || ^8", + "psalm/phar": "^3.11.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "files": [ + "lib/functions.php" + ], + "psr-4": { + "Amp\\ByteStream\\": "lib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A stream abstraction to make working with non-blocking I/O simple.", + "homepage": "http://amphp.org/byte-stream", + "keywords": [ + "amp", + "amphp", + "async", + "io", + "non-blocking", + "stream" + ], + "support": { + "irc": "irc://irc.freenode.org/amphp", + "issues": "https://github.com/amphp/byte-stream/issues", + "source": "https://github.com/amphp/byte-stream/tree/v1.8.1" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2021-03-30T17:13:30+00:00" + }, + { + "name": "composer/pcre", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "4bff79ddd77851fe3cdd11616ed3f92841ba5bd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/4bff79ddd77851fe3cdd11616ed3f92841ba5bd2", + "reference": "4bff79ddd77851fe3cdd11616ed3f92841ba5bd2", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.3", + "phpstan/phpstan-strict-rules": "^1.1", + "symfony/phpunit-bridge": "^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.1.0" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2022-11-17T09:50:14+00:00" + }, + { + "name": "composer/semver", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/3953f23262f2bff1919fc82183ad9acb13ff62c9", + "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.4", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.3.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2022-04-01T19:23:25+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "ced299686f41dce890debac69273b47ffe98a40c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/ced299686f41dce890debac69273b47ffe98a40c", + "reference": "ced299686f41dce890debac69273b47ffe98a40c", + "shasum": "" + }, + "require": { + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "symfony/phpunit-bridge": "^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.3" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2022-02-25T21:32:43+00:00" + }, + { + "name": "dnoegel/php-xdg-base-dir", + "version": "v0.1.1", + "source": { + "type": "git", + "url": "https://github.com/dnoegel/php-xdg-base-dir.git", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~7.0|~6.0|~5.0|~4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "XdgBaseDir\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "implementation of xdg base directory specification for php", + "support": { + "issues": "https://github.com/dnoegel/php-xdg-base-dir/issues", + "source": "https://github.com/dnoegel/php-xdg-base-dir/tree/v0.1.1" + }, + "time": "2019-12-04T15:06:13+00:00" + }, + { + "name": "felixfbecker/advanced-json-rpc", + "version": "v3.2.1", + "source": { + "type": "git", + "url": "https://github.com/felixfbecker/php-advanced-json-rpc.git", + "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/felixfbecker/php-advanced-json-rpc/zipball/b5f37dbff9a8ad360ca341f3240dc1c168b45447", + "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447", + "shasum": "" + }, + "require": { + "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", + "php": "^7.1 || ^8.0", + "phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "AdvancedJsonRpc\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Felix Becker", + "email": "felix.b@outlook.com" + } + ], + "description": "A more advanced JSONRPC implementation", + "support": { + "issues": "https://github.com/felixfbecker/php-advanced-json-rpc/issues", + "source": "https://github.com/felixfbecker/php-advanced-json-rpc/tree/v3.2.1" + }, + "time": "2021-06-11T22:34:44+00:00" + }, + { + "name": "felixfbecker/language-server-protocol", + "version": "v1.5.2", + "source": { + "type": "git", + "url": "https://github.com/felixfbecker/php-language-server-protocol.git", + "reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/6e82196ffd7c62f7794d778ca52b69feec9f2842", + "reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpstan/phpstan": "*", + "squizlabs/php_codesniffer": "^3.1", + "vimeo/psalm": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "LanguageServerProtocol\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Felix Becker", + "email": "felix.b@outlook.com" + } + ], + "description": "PHP classes for the Language Server Protocol", + "keywords": [ + "language", + "microsoft", + "php", + "server" + ], + "support": { + "issues": "https://github.com/felixfbecker/php-language-server-protocol/issues", + "source": "https://github.com/felixfbecker/php-language-server-protocol/tree/v1.5.2" + }, + "time": "2022-03-02T22:36:06+00:00" + }, + { + "name": "fidry/cpu-core-counter", + "version": "0.5.1", + "source": { + "type": "git", + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "b58e5a3933e541dc286cc91fc4f3898bbc6f1623" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/b58e5a3933e541dc286cc91fc4f3898bbc6f1623", + "reference": "b58e5a3933e541dc286cc91fc4f3898bbc6f1623", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "fidry/makefile": "^0.2.0", + "phpstan/extension-installer": "^1.2.0", + "phpstan/phpstan": "^1.9.2", + "phpstan/phpstan-deprecation-rules": "^1.0.0", + "phpstan/phpstan-phpunit": "^1.2.2", + "phpstan/phpstan-strict-rules": "^1.4.4", + "phpunit/phpunit": "^9.5.26 || ^8.5.31", + "theofidry/php-cs-fixer-config": "^1.0", + "webmozarts/strict-phpunit": "^7.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Fidry\\CpuCoreCounter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com" + } + ], + "description": "Tiny utility to get the number of CPU cores.", + "keywords": [ + "CPU", + "core" + ], + "support": { + "issues": "https://github.com/theofidry/cpu-core-counter/issues", + "source": "https://github.com/theofidry/cpu-core-counter/tree/0.5.1" + }, + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2022-12-24T12:35:10+00:00" + }, { "name": "masterminds/html5", "version": "2.8.0", @@ -7402,6 +8234,57 @@ ], "time": "2023-03-08T13:26:56+00:00" }, + { + "name": "netresearch/jsonmapper", + "version": "v4.2.0", + "source": { + "type": "git", + "url": "https://github.com/cweiske/jsonmapper.git", + "reference": "f60565f8c0566a31acf06884cdaa591867ecc956" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/f60565f8c0566a31acf06884cdaa591867ecc956", + "reference": "f60565f8c0566a31acf06884cdaa591867ecc956", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "~7.5 || ~8.0 || ~9.0", + "squizlabs/php_codesniffer": "~3.5" + }, + "type": "library", + "autoload": { + "psr-0": { + "JsonMapper": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "OSL-3.0" + ], + "authors": [ + { + "name": "Christian Weiske", + "email": "cweiske@cweiske.de", + "homepage": "http://github.com/cweiske/jsonmapper/", + "role": "Developer" + } + ], + "description": "Map nested JSON structures onto PHP classes", + "support": { + "email": "cweiske@cweiske.de", + "issues": "https://github.com/cweiske/jsonmapper/issues", + "source": "https://github.com/cweiske/jsonmapper/tree/v4.2.0" + }, + "time": "2023-04-09T17:37:40+00:00" + }, { "name": "nikic/php-parser", "version": "v4.15.4", @@ -8954,6 +9837,69 @@ ], "time": "2020-09-28T06:39:44+00:00" }, + { + "name": "spatie/array-to-xml", + "version": "3.1.5", + "source": { + "type": "git", + "url": "https://github.com/spatie/array-to-xml.git", + "reference": "13f76acef5362d15c71ae1ac6350cc3df5e25e43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/13f76acef5362d15c71ae1ac6350cc3df5e25e43", + "reference": "13f76acef5362d15c71ae1ac6350cc3df5e25e43", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": "^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.2", + "pestphp/pest": "^1.21", + "spatie/pest-plugin-snapshots": "^1.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\ArrayToXml\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://freek.dev", + "role": "Developer" + } + ], + "description": "Convert an array to xml", + "homepage": "https://github.com/spatie/array-to-xml", + "keywords": [ + "array", + "convert", + "xml" + ], + "support": { + "source": "https://github.com/spatie/array-to-xml/tree/3.1.5" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2022-12-24T13:43:51+00:00" + }, { "name": "symfony/browser-kit", "version": "v6.2.7", @@ -9541,6 +10487,112 @@ } ], "time": "2021-07-28T10:34:58+00:00" + }, + { + "name": "vimeo/psalm", + "version": "5.11.0", + "source": { + "type": "git", + "url": "https://github.com/vimeo/psalm.git", + "reference": "c9b192ab8400fdaf04b2b13d110575adc879aa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/c9b192ab8400fdaf04b2b13d110575adc879aa90", + "reference": "c9b192ab8400fdaf04b2b13d110575adc879aa90", + "shasum": "" + }, + "require": { + "amphp/amp": "^2.4.2", + "amphp/byte-stream": "^1.5", + "composer-runtime-api": "^2", + "composer/semver": "^1.4 || ^2.0 || ^3.0", + "composer/xdebug-handler": "^2.0 || ^3.0", + "dnoegel/php-xdg-base-dir": "^0.1.1", + "ext-ctype": "*", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-tokenizer": "*", + "felixfbecker/advanced-json-rpc": "^3.1", + "felixfbecker/language-server-protocol": "^1.5.2", + "fidry/cpu-core-counter": "^0.4.1 || ^0.5.1", + "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", + "nikic/php-parser": "^4.14", + "php": "^7.4 || ~8.0.0 || ~8.1.0 || ~8.2.0", + "sebastian/diff": "^4.0 || ^5.0", + "spatie/array-to-xml": "^2.17.0 || ^3.0", + "symfony/console": "^4.1.6 || ^5.0 || ^6.0", + "symfony/filesystem": "^5.4 || ^6.0" + }, + "provide": { + "psalm/psalm": "self.version" + }, + "require-dev": { + "amphp/phpunit-util": "^2.0", + "bamarni/composer-bin-plugin": "^1.4", + "brianium/paratest": "^6.9", + "ext-curl": "*", + "mockery/mockery": "^1.5", + "nunomaduro/mock-final-classes": "^1.1", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpdoc-parser": "^1.6", + "phpunit/phpunit": "^9.6", + "psalm/plugin-mockery": "^1.1", + "psalm/plugin-phpunit": "^0.18", + "slevomat/coding-standard": "^8.4", + "squizlabs/php_codesniffer": "^3.6", + "symfony/process": "^4.4 || ^5.0 || ^6.0" + }, + "suggest": { + "ext-curl": "In order to send data to shepherd", + "ext-igbinary": "^2.0.5 is required, used to serialize caching data" + }, + "bin": [ + "psalm", + "psalm-language-server", + "psalm-plugin", + "psalm-refactor", + "psalter" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev", + "dev-4.x": "4.x-dev", + "dev-3.x": "3.x-dev", + "dev-2.x": "2.x-dev", + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psalm\\": "src/Psalm/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthew Brown" + } + ], + "description": "A static analysis tool for finding errors in PHP applications", + "keywords": [ + "code", + "inspection", + "php", + "static analysis" + ], + "support": { + "issues": "https://github.com/vimeo/psalm/issues", + "source": "https://github.com/vimeo/psalm/tree/5.11.0" + }, + "time": "2023-05-04T21:35:44+00:00" } ], "aliases": [], @@ -9551,6 +10603,7 @@ "platform": { "php": ">=8.1", "ext-ctype": "*", + "ext-dom": "*", "ext-iconv": "*" }, "platform-dev": [], diff --git a/config/bundles.php b/config/bundles.php index 887a0e3..8f6d413 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -12,4 +12,5 @@ return [ Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true], Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true], Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true], + EasyCorp\Bundle\EasyAdminBundle\EasyAdminBundle::class => ['all' => true], ]; diff --git a/config/packages/uid.yaml b/config/packages/uid.yaml new file mode 100644 index 0000000..0152094 --- /dev/null +++ b/config/packages/uid.yaml @@ -0,0 +1,4 @@ +framework: + uid: + default_uuid_version: 7 + time_based_uuid_version: 7 diff --git a/migrations/Version20230506144146.php b/migrations/Version20230506144146.php new file mode 100644 index 0000000..7deafa4 --- /dev/null +++ b/migrations/Version20230506144146.php @@ -0,0 +1,54 @@ +addSql('CREATE SEQUENCE episode_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE podcast_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE TABLE episode (id INT NOT NULL, podcast_id INT NOT NULL, title VARCHAR(255) NOT NULL, description TEXT NOT NULL, audio_filename VARCHAR(255) DEFAULT NULL, publication_date TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_DDAA1CDA786136AB ON episode (podcast_id)'); + $this->addSql('CREATE TABLE podcast (id INT NOT NULL, name VARCHAR(255) NOT NULL, website VARCHAR(255) NOT NULL, description TEXT NOT NULL, author VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, logo_filename VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE TABLE messenger_messages (id BIGSERIAL NOT NULL, body TEXT NOT NULL, headers TEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, available_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, delivered_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_75EA56E0FB7336F0 ON messenger_messages (queue_name)'); + $this->addSql('CREATE INDEX IDX_75EA56E0E3BD61CE ON messenger_messages (available_at)'); + $this->addSql('CREATE INDEX IDX_75EA56E016BA31DB ON messenger_messages (delivered_at)'); + $this->addSql('CREATE OR REPLACE FUNCTION notify_messenger_messages() RETURNS TRIGGER AS $$ + BEGIN + PERFORM pg_notify(\'messenger_messages\', NEW.queue_name::text); + RETURN NEW; + END; + $$ LANGUAGE plpgsql;'); + $this->addSql('DROP TRIGGER IF EXISTS notify_trigger ON messenger_messages;'); + $this->addSql('CREATE TRIGGER notify_trigger AFTER INSERT OR UPDATE ON messenger_messages FOR EACH ROW EXECUTE PROCEDURE notify_messenger_messages();'); + $this->addSql('ALTER TABLE episode ADD CONSTRAINT FK_DDAA1CDA786136AB FOREIGN KEY (podcast_id) REFERENCES podcast (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE SCHEMA public'); + $this->addSql('DROP SEQUENCE episode_id_seq CASCADE'); + $this->addSql('DROP SEQUENCE podcast_id_seq CASCADE'); + $this->addSql('ALTER TABLE episode DROP CONSTRAINT FK_DDAA1CDA786136AB'); + $this->addSql('DROP TABLE episode'); + $this->addSql('DROP TABLE podcast'); + $this->addSql('DROP TABLE messenger_messages'); + } +} diff --git a/migrations/Version20230508171219.php b/migrations/Version20230508171219.php new file mode 100644 index 0000000..c3ddbf5 --- /dev/null +++ b/migrations/Version20230508171219.php @@ -0,0 +1,36 @@ +addSql('ALTER TABLE podcast ADD slug VARCHAR(255)'); + $this->addSql('UPDATE podcast SET slug = LOWER(CAST(id AS VARCHAR))'); + $this->addSql('ALTER TABLE podcast ALTER COLUMN slug SET NOT NULL'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_D7E805BD989D9B62 ON podcast (slug)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE SCHEMA public'); + $this->addSql('DROP INDEX UNIQ_D7E805BD989D9B62'); + $this->addSql('ALTER TABLE podcast DROP slug'); + } +} diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 0000000..02789b8 --- /dev/null +++ b/psalm.xml @@ -0,0 +1,16 @@ + + + + + + + + + diff --git a/public/uploads/files/.gitkeep b/public/uploads/files/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/public/uploads/images/.gitkeep b/public/uploads/images/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/Constants.php b/src/Constants.php new file mode 100644 index 0000000..5174404 --- /dev/null +++ b/src/Constants.php @@ -0,0 +1,12 @@ +container->get(AdminUrlGenerator::class); + return $this->redirect($adminUrlGenerator->setController(PodcastCrudController::class)->generateUrl()); + } + + public function configureDashboard(): Dashboard + { + return Dashboard::new()->setTitle('LSBC'); + } + + public function configureMenuItems(): iterable + { + yield MenuItem::linkToCrud('Podcasts', 'fas fa-list', Podcast::class); + yield MenuItem::linkToCrud('Episodes', 'fas fa-list', Episode::class); + } +} diff --git a/src/Controller/Admin/EpisodeCrudController.php b/src/Controller/Admin/EpisodeCrudController.php new file mode 100644 index 0000000..2f5f024 --- /dev/null +++ b/src/Controller/Admin/EpisodeCrudController.php @@ -0,0 +1,33 @@ +setUploadDir(Constants::BASE_PUBLIC_DIR . Constants::FILES_BASE_PATH) + ->setBasePath(Constants::FILES_BASE_PATH), + ]; + } +} diff --git a/src/Controller/Admin/Field/FileField.php b/src/Controller/Admin/Field/FileField.php new file mode 100644 index 0000000..2bca6c3 --- /dev/null +++ b/src/Controller/Admin/Field/FileField.php @@ -0,0 +1,80 @@ + + */ +final class FileField implements FieldInterface +{ + use FieldTrait; + + public const OPTION_BASE_PATH = 'basePath'; + public const OPTION_UPLOAD_DIR = 'uploadDir'; + public const OPTION_UPLOADED_FILE_NAME_PATTERN = 'uploadedFileNamePattern'; + + /** + * @param TranslatableInterface|string|false|null $label + */ + public static function new(string $propertyName, $label = null): self + { + return (new self()) + ->setProperty($propertyName) + ->setLabel($label) + ->setTemplatePath('admin/field/file.html.twig') + ->setFormType(FileUploadType::class) + ->addCssClass('field-file') + ->addJsFiles(Asset::fromEasyAdminAssetPackage('field-file-upload.js')) + ->setTextAlign(TextAlign::CENTER) + ->setCustomOption(self::OPTION_BASE_PATH, null) + ->setCustomOption(self::OPTION_UPLOAD_DIR, null) + ->setCustomOption(self::OPTION_UPLOADED_FILE_NAME_PATTERN, '[name].[extension]'); + } + + public function setBasePath(string $path): self + { + $this->setCustomOption(self::OPTION_BASE_PATH, $path); + + return $this; + } + + /** + * Relative to project's root directory (e.g. use 'public/uploads/' for `/public/uploads/`) + * Default upload dir: `/public/uploads/images/`. + */ + public function setUploadDir(string $uploadDirPath): self + { + $this->setCustomOption(self::OPTION_UPLOAD_DIR, $uploadDirPath); + + return $this; + } + + /** + * @param string|\Closure $patternOrCallable + * + * If it's a string, uploaded files will be renamed according to the given pattern. + * The pattern can include the following special values: + * [day] [month] [year] [timestamp] + * [name] [slug] [extension] [contenthash] + * [randomhash] [uuid] [ulid] + * (e.g. [year]/[month]/[day]/[slug]-[contenthash].[extension]) + * + * If it's a callable, you will be passed the Symfony's UploadedFile instance and you must + * return a string with the new filename. + * (e.g. fn(UploadedFile $file) => sprintf('upload_%d_%s.%s', random_int(1, 999), $file->getFilename(), $file->guessExtension())) + */ + public function setUploadedFileNamePattern($patternOrCallable): self + { + $this->setCustomOption(self::OPTION_UPLOADED_FILE_NAME_PATTERN, $patternOrCallable); + + return $this; + } +} + diff --git a/src/Controller/Admin/PodcastCrudController.php b/src/Controller/Admin/PodcastCrudController.php new file mode 100644 index 0000000..3d05b03 --- /dev/null +++ b/src/Controller/Admin/PodcastCrudController.php @@ -0,0 +1,34 @@ +setUploadDir(Constants::BASE_PUBLIC_DIR . Constants::IMAGES_BASE_PATH) + ->setBasePath(Constants::IMAGES_BASE_PATH), + ]; + } +} diff --git a/src/Controller/FeedController.php b/src/Controller/FeedController.php new file mode 100644 index 0000000..be9c140 --- /dev/null +++ b/src/Controller/FeedController.php @@ -0,0 +1,51 @@ +findAll(); + return $this->render('feed/index.html.twig', ['podcasts' => $podcasts]); + } + + #[Route('/podcasts/{slug}', name: 'app_podcast')] + public function podcast( + string $slug, + PodcastRepository $podcastRepository, + ): Response + { + $podcast = $podcastRepository->findOneBy(['slug' => $slug]); + return $this->render('feed/podcast.html.twig', ['podcast' => $podcast]); + } + + #[Route('/podcasts/{slug}/feed', name: 'app_podcast_feed')] + public function podcastFeed( + PodcastRepository $podcastRepository, + FeedService $feedService + ): Response + { + $podcast = $podcastRepository->find(1); + if ($podcast === null) + return $this->render('404.html.twig'); + + $xml = $feedService->generate($podcast); + if ($xml === false) + return $this->render('500.html.twig'); + + $response = new Response(); + $response->headers->set('Content-Type', 'application/rss+xml'); + $response->setContent($xml); + return $response; + } +} diff --git a/src/Entity/Episode.php b/src/Entity/Episode.php new file mode 100644 index 0000000..f06998d --- /dev/null +++ b/src/Entity/Episode.php @@ -0,0 +1,109 @@ +id; + } + + public function getTitle(): string + { + return $this->title; + } + + public function setTitle(string $title): self + { + $this->title = $title; + + return $this; + } + + public function getDescription(): string + { + return $this->description; + } + + public function setDescription(string $description): self + { + $this->description = $description; + + return $this; + } + + public function getAudioFilename(): ?string + { + return $this->audioFilename; + } + + public function setAudioFilename(?string $audioFilename): self + { + $this->audioFilename = $audioFilename; + + return $this; + } + + public function getPodcast(): Podcast + { + return $this->podcast; + } + + public function setPodcast(Podcast $podcast): self + { + $this->podcast = $podcast; + + return $this; + } + + public function getPublicationDate(): \DateTimeInterface + { + return $this->publicationDate; + } + + public function setPublicationDate(\DateTimeInterface $publicationDate): self + { + $this->publicationDate = $publicationDate; + + return $this; + } + + public function getAudioUrl(): ?string + { + if ($this->audioFilename === null) { + return null; + } + + return Constants::FILES_BASE_PATH . $this->audioFilename; + } +} diff --git a/src/Entity/Podcast.php b/src/Entity/Podcast.php new file mode 100644 index 0000000..1f172e2 --- /dev/null +++ b/src/Entity/Podcast.php @@ -0,0 +1,179 @@ +episodes = new ArrayCollection(); + } + + public function __toString(): string + { + return $this->name; + } + + public function getId(): int + { + return $this->id; + } + + public function getName(): string + { + return $this->name; + } + + public function setName(string $name): self + { + $this->name = $name; + + return $this; + } + + public function getSlug(): string + { + return $this->slug; + } + + public function setSlug(string $slug): self + { + $this->slug = $slug; + + return $this; + } + + #[ORM\PrePersist] + public function generateSlug(SluggerInterface $slugger): void + { + $this->slug = $this->id . '-' . $slugger->slug($this->name)->lower(); + } + + /** + * @return Collection + */ + public function getEpisodes(): Collection + { + return $this->episodes; + } + + public function addEpisode(Episode $episode): self + { + if (!$this->episodes->contains($episode)) { + $this->episodes->add($episode); + $episode->setPodcast($this); + } + + return $this; + } + + public function removeEpisode(Episode $episode): self + { + if ($this->episodes->removeElement($episode)) { + // set the owning side to null (unless already changed) + if ($episode->getPodcast() === $this) { + $episode->setPodcast(null); + } + } + + return $this; + } + + public function getWebsite(): string + { + return $this->website; + } + + public function setWebsite(string $website): self + { + $this->website = $website; + + return $this; + } + + public function getDescription(): string + { + return $this->description; + } + + public function setDescription(string $description): self + { + $this->description = $description; + + return $this; + } + + public function getAuthor(): string + { + return $this->author; + } + + public function setAuthor(string $authorName): self + { + $this->author = $authorName; + + return $this; + } + + public function getEmail(): string + { + return $this->email; + } + + public function setEmail(string $email): self + { + $this->email = $email; + + return $this; + } + + public function getLogoFilename(): string + { + return $this->logoFilename; + } + + public function setLogoFilename(string $logoFilename): self + { + $this->logoFilename = $logoFilename; + + return $this; + } +} diff --git a/src/Repository/EpisodeRepository.php b/src/Repository/EpisodeRepository.php new file mode 100644 index 0000000..59c5196 --- /dev/null +++ b/src/Repository/EpisodeRepository.php @@ -0,0 +1,66 @@ + + * + * @method Episode|null find($id, $lockMode = null, $lockVersion = null) + * @method Episode|null findOneBy(array $criteria, array $orderBy = null) + * @method Episode[] findAll() + * @method Episode[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class EpisodeRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Episode::class); + } + + public function save(Episode $entity, bool $flush = false): void + { + $this->getEntityManager()->persist($entity); + + if ($flush) { + $this->getEntityManager()->flush(); + } + } + + public function remove(Episode $entity, bool $flush = false): void + { + $this->getEntityManager()->remove($entity); + + if ($flush) { + $this->getEntityManager()->flush(); + } + } + +// /** +// * @return Episode[] Returns an array of Episode objects +// */ +// public function findByExampleField($value): array +// { +// return $this->createQueryBuilder('e') +// ->andWhere('e.exampleField = :val') +// ->setParameter('val', $value) +// ->orderBy('e.id', 'ASC') +// ->setMaxResults(10) +// ->getQuery() +// ->getResult() +// ; +// } + +// public function findOneBySomeField($value): ?Episode +// { +// return $this->createQueryBuilder('e') +// ->andWhere('e.exampleField = :val') +// ->setParameter('val', $value) +// ->getQuery() +// ->getOneOrNullResult() +// ; +// } +} diff --git a/src/Repository/PodcastRepository.php b/src/Repository/PodcastRepository.php new file mode 100644 index 0000000..d29dcf6 --- /dev/null +++ b/src/Repository/PodcastRepository.php @@ -0,0 +1,66 @@ + + * + * @method Podcast|null find($id, $lockMode = null, $lockVersion = null) + * @method Podcast|null findOneBy(array $criteria, array $orderBy = null) + * @method Podcast[] findAll() + * @method Podcast[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class PodcastRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Podcast::class); + } + + public function save(Podcast $entity, bool $flush = false): void + { + $this->getEntityManager()->persist($entity); + + if ($flush) { + $this->getEntityManager()->flush(); + } + } + + public function remove(Podcast $entity, bool $flush = false): void + { + $this->getEntityManager()->remove($entity); + + if ($flush) { + $this->getEntityManager()->flush(); + } + } + +// /** +// * @return Podcast[] Returns an array of Podcast objects +// */ +// public function findByExampleField($value): array +// { +// return $this->createQueryBuilder('p') +// ->andWhere('p.exampleField = :val') +// ->setParameter('val', $value) +// ->orderBy('p.id', 'ASC') +// ->setMaxResults(10) +// ->getQuery() +// ->getResult() +// ; +// } + +// public function findOneBySomeField($value): ?Podcast +// { +// return $this->createQueryBuilder('p') +// ->andWhere('p.exampleField = :val') +// ->setParameter('val', $value) +// ->getQuery() +// ->getOneOrNullResult() +// ; +// } +} diff --git a/src/Service/FeedService.php b/src/Service/FeedService.php new file mode 100644 index 0000000..d6d96ea --- /dev/null +++ b/src/Service/FeedService.php @@ -0,0 +1,81 @@ +entityManager = $entityManager; + $this->parameterBag = $parameterBag; + $this->baseFeedUrl = $urlGenerator->generate( + 'app_index', + [], + UrlGeneratorInterface::ABSOLUTE_URL + ) . Constants::FILES_BASE_PATH; + } + + public const DOCUMENT_VERSION = '2.0'; + + public function generate(Podcast $podcast): string|false + { + $document = new DOMDocument(); + $rssElement = new DOMElement('rss'); + $document->appendChild($rssElement); + $rssElement->setAttribute('version', $this::DOCUMENT_VERSION); + $channelElement = new DOMElement('channel'); + $rssElement->appendChild($channelElement); + $titleElement = new DOMElement('title'); + $channelElement->appendChild($titleElement); + $titleElement->appendChild(new DOMText($podcast->getName())); + $channelElement->appendChild(new DOMElement('description', $podcast->getDescription())); + $channelElement->appendChild(new DOMElement('link', $podcast->getWebsite())); + + $episodes = $this->entityManager->createQuery( + 'SELECT e FROM App\Entity\Episode e' + .' WHERE e.podcast = :podcastId' + .' AND e.audioFilename IS NOT NULL' + )->setParameter('podcastId', $podcast->getId())->getResult(); + foreach ($episodes as $episode) { + $audioUrl = $this->baseFeedUrl . $episode->getAudioFilename(); // Also used as ID. + $itemElement = new DOMElement('item'); + $channelElement->appendChild($itemElement); + $titleElement = new DOMElement('title'); + $itemElement->appendChild($titleElement); + $titleElement->appendChild(new DOMText($episode->getTitle())); + $itemElement->appendChild(new DOMElement('description', $episode->getDescription())); + $itemElement->appendChild(new DOMElement('link', $audioUrl)); + $itemElement->appendChild(new DOMElement('guid', $audioUrl)); + $enclosureElement = new DOMElement('enclosure'); + $itemElement->appendChild($enclosureElement); + $enclosureElement->setAttribute('url', $audioUrl); + $filepath = + $this->parameterBag->get('kernel.project_dir') + . '/' + . Constants::BASE_PUBLIC_DIR + . Constants::FILES_BASE_PATH + . $episode->getAudioFilename(); + $enclosureElement->setAttribute('type', mime_content_type($filepath)); + $enclosureElement->setAttribute('length', (string) filesize($filepath)); + } + + return $document->saveXML(); + } +} diff --git a/symfony.lock b/symfony.lock index 5ef3ac8..caf600c 100644 --- a/symfony.lock +++ b/symfony.lock @@ -35,6 +35,15 @@ "migrations/.gitignore" ] }, + "easycorp/easyadmin-bundle": { + "version": "4.6", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "3.0", + "ref": "b131e6cbfe1b898a508987851963fff485986285" + } + }, "phpunit/phpunit": { "version": "9.6", "recipe": { @@ -239,6 +248,18 @@ "templates/base.html.twig" ] }, + "symfony/uid": { + "version": "6.2", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "6.2", + "ref": "d294ad4add3e15d7eb1bae0221588ca89b38e558" + }, + "files": [ + "config/packages/uid.yaml" + ] + }, "symfony/validator": { "version": "6.2", "recipe": { diff --git a/templates/404.html.twig b/templates/404.html.twig new file mode 100644 index 0000000..ba6cda0 --- /dev/null +++ b/templates/404.html.twig @@ -0,0 +1,6 @@ +{% extends "base.html.twig" %} + +{% block body %} +

404

+

Page not found. 🤔🔎

+{% endblock %} diff --git a/templates/500.html.twig b/templates/500.html.twig new file mode 100644 index 0000000..719e26f --- /dev/null +++ b/templates/500.html.twig @@ -0,0 +1,6 @@ +{% extends "base.html.twig" %} + +{% block body %} +

500

+

An error occured, sorry! 😭

+{% endblock %} diff --git a/templates/admin/field/file.html.twig b/templates/admin/field/file.html.twig new file mode 100644 index 0000000..4a3c780 --- /dev/null +++ b/templates/admin/field/file.html.twig @@ -0,0 +1,11 @@ +{# @var ea \EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext #} +{# @var field \EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto #} +{# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #} +{% set files = field.formattedValue %} +{% if files is not iterable %} + {% set files = [files] %} +{% endif %} + +{% for file in files %} + {{ file }} +{% endfor %} diff --git a/templates/base.html.twig b/templates/base.html.twig index d4f83f7..bf1895f 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -2,13 +2,16 @@ - {% block title %}Welcome!{% endblock %} + + {% block title %}LSBC{% endblock %} {# Run `composer require symfony/webpack-encore-bundle` to start using Symfony UX #} {% block stylesheets %} + {{ encore_entry_link_tags('app') }} {% endblock %} - {% block javascripts %} {{ encore_entry_script_tags('app') }} {% endblock %} diff --git a/templates/feed/index.html.twig b/templates/feed/index.html.twig new file mode 100644 index 0000000..1837e8e --- /dev/null +++ b/templates/feed/index.html.twig @@ -0,0 +1,10 @@ +{% extends 'base.html.twig' %} + +{% block body %} +

Podcasts available on this LSBC instance:

+ +{% endblock %} diff --git a/templates/feed/podcast.html.twig b/templates/feed/podcast.html.twig new file mode 100644 index 0000000..dd3e0ec --- /dev/null +++ b/templates/feed/podcast.html.twig @@ -0,0 +1,11 @@ +{% extends 'base.html.twig' %} + +{% block body %} +

{{ podcast.name }}

+

+ By {{ podcast.author }}.
+ Contact: {{ podcast.email }}
+ {% set feed = url('app_podcast_feed', {slug: podcast.slug}) %} + Feed URL: {{ feed }}
+

+{% endblock %}