diff --git a/config/packages/doctrine.yaml b/config/packages/doctrine.yaml index d199bf9..6505d23 100644 --- a/config/packages/doctrine.yaml +++ b/config/packages/doctrine.yaml @@ -41,7 +41,13 @@ doctrine: is_bundle: false dir: '%kernel.project_dir%/src/Monitor/Framework/Entity' prefix: 'App\Monitor\Framework\Entity' - alias: Download + alias: Monitor + EventLog: + type: attribute + is_bundle: false + dir: '%kernel.project_dir%/src/EventLog/Framework/Entity' + prefix: 'App\EventLog\Framework\Entity' + alias: EventLog controller_resolver: auto_mapping: false diff --git a/migrations/Version20251101211617.php b/migrations/Version20251101211617.php new file mode 100644 index 0000000..a623552 --- /dev/null +++ b/migrations/Version20251101211617.php @@ -0,0 +1,35 @@ +addSql(<<<'SQL' + CREATE TABLE event_log (id INT AUTO_INCREMENT NOT NULL, type VARCHAR(255) DEFAULT NULL, message LONGTEXT DEFAULT NULL, context JSON DEFAULT NULL COMMENT '(DC2Type:json)', PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB + SQL); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql(<<<'SQL' + DROP TABLE event_log + SQL); + } +} diff --git a/migrations/Version20251102004627.php b/migrations/Version20251102004627.php new file mode 100644 index 0000000..693b0f5 --- /dev/null +++ b/migrations/Version20251102004627.php @@ -0,0 +1,47 @@ +addSql(<<<'SQL' + ALTER TABLE event_log ADD user_id INT DEFAULT NULL + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE event_log ADD CONSTRAINT FK_9EF0AD16A76ED395 FOREIGN KEY (user_id) REFERENCES user (id) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX IDX_9EF0AD16A76ED395 ON event_log (user_id) + SQL); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql(<<<'SQL' + ALTER TABLE event_log DROP FOREIGN KEY FK_9EF0AD16A76ED395 + SQL); + $this->addSql(<<<'SQL' + DROP INDEX IDX_9EF0AD16A76ED395 ON event_log + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE event_log DROP user_id + SQL); + } +} diff --git a/src/Download/Action/Handler/DeleteDownloadHandler.php b/src/Download/Action/Handler/DeleteDownloadHandler.php index 4f139d9..5323b5f 100644 --- a/src/Download/Action/Handler/DeleteDownloadHandler.php +++ b/src/Download/Action/Handler/DeleteDownloadHandler.php @@ -4,17 +4,22 @@ namespace App\Download\Action\Handler; use App\Download\Action\Command\DeleteDownloadCommand; use App\Download\Action\Result\DeleteDownloadResult; +use App\Download\DownloadEvents; use App\Download\Framework\Repository\DownloadRepository; +use App\EventLog\Action\Command\AddEventLogCommand; use App\Library\Action\Command\DeleteMediaFileCommand; use App\Library\Action\Handler\DeleteMediaFileHandler; +use App\Monitor\MonitorEvents; use OneToMany\RichBundle\Contract\CommandInterface; use OneToMany\RichBundle\Contract\HandlerInterface; use OneToMany\RichBundle\Contract\ResultInterface; +use Symfony\Component\Messenger\MessageBusInterface; /** @implements HandlerInterface */ readonly class DeleteDownloadHandler implements HandlerInterface { public function __construct( + private MessageBusInterface $bus, private DownloadRepository $downloadRepository, private DeleteMediaFileHandler $deleteMediaFileHandler, ) {} @@ -31,6 +36,13 @@ readonly class DeleteDownloadHandler implements HandlerInterface } $this->downloadRepository->delete($command->downloadId); + $this->bus->dispatch(new AddEventLogCommand( + $download->getUser(), + DownloadEvents::DOWNLOAD_DELETED->type(), + DownloadEvents::DOWNLOAD_DELETED->message(), + (array) $download + )); + return new DeleteDownloadResult( status: 200, message: 'Success', diff --git a/src/Download/Action/Handler/DownloadMediaHandler.php b/src/Download/Action/Handler/DownloadMediaHandler.php index d8d2726..5700193 100644 --- a/src/Download/Action/Handler/DownloadMediaHandler.php +++ b/src/Download/Action/Handler/DownloadMediaHandler.php @@ -4,18 +4,22 @@ namespace App\Download\Action\Handler; use App\Download\Action\Command\DownloadMediaCommand; use App\Download\Action\Result\DownloadMediaResult; +use App\Download\DownloadEvents; use App\Download\Framework\Repository\DownloadRepository; use App\Download\Downloader\DownloaderInterface; +use App\EventLog\Action\Command\AddEventLogCommand; use App\User\Framework\Repository\UserRepository; use OneToMany\RichBundle\Contract\CommandInterface; use OneToMany\RichBundle\Contract\HandlerInterface; use OneToMany\RichBundle\Contract\ResultInterface; use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException; +use Symfony\Component\Messenger\MessageBusInterface; /** @implements HandlerInterface */ readonly class DownloadMediaHandler implements HandlerInterface { public function __construct( + private MessageBusInterface $bus, private DownloaderInterface $downloader, private DownloadRepository $downloadRepository, private UserRepository $userRepository, @@ -23,9 +27,17 @@ readonly class DownloadMediaHandler implements HandlerInterface public function handle(CommandInterface $command): ResultInterface { + $user = $this->userRepository->find($command->userId); + $this->bus->dispatch(new AddEventLogCommand( + $user, + DownloadEvents::DOWNLOAD_STARTED->type(), + DownloadEvents::DOWNLOAD_STARTED->message(), + (array) $command + )); + if (null === $command->downloadId) { $download = $this->downloadRepository->insert( - $this->userRepository->find($command->userId), + $user, $command->url, $command->title, $command->filename, @@ -57,6 +69,12 @@ readonly class DownloadMediaHandler implements HandlerInterface throw new UnrecoverableMessageHandlingException($exception->getMessage(), 500); } + $this->bus->dispatch(new AddEventLogCommand( + $user, + DownloadEvents::DOWNLOAD_FINISHED->type(), + DownloadEvents::DOWNLOAD_FINISHED->message(), + (array) $command + )); return new DownloadMediaResult(200, "Success."); } } diff --git a/src/Download/DownloadEvents.php b/src/Download/DownloadEvents.php new file mode 100644 index 0000000..8a7e8c9 --- /dev/null +++ b/src/Download/DownloadEvents.php @@ -0,0 +1,34 @@ + 'download_added', + self::DOWNLOAD_STARTED => 'download_started', + self::DOWNLOAD_FINISHED => 'download_finished', + self::DOWNLOAD_DELETED => 'download_deleted', + self::DOWNLOAD_ERROR => 'download_error', + }; + } + + public function message(): string + { + return match ($this) { + self::DOWNLOAD_ADDED => 'A new download has been added.', + self::DOWNLOAD_STARTED => 'A download has started.', + self::DOWNLOAD_FINISHED => 'A download has finished.', + self::DOWNLOAD_DELETED => 'A download has been deleted.', + self::DOWNLOAD_ERROR => 'A download has encountered an error.', + }; + } +} diff --git a/src/Download/Downloader/ProcessDownloader.php b/src/Download/Downloader/ProcessDownloader.php index 5931181..99641e1 100644 --- a/src/Download/Downloader/ProcessDownloader.php +++ b/src/Download/Downloader/ProcessDownloader.php @@ -4,9 +4,12 @@ namespace App\Download\Downloader; use App\Base\Service\Broadcaster; use App\Base\Service\MediaFiles; +use App\Download\DownloadEvents; use App\Download\Framework\Entity\Download; +use App\EventLog\Action\Command\AddEventLogCommand; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Cache\Adapter\RedisAdapter; +use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Process\Exception\ProcessFailedException; use Symfony\Component\Process\Process; use Symfony\Contracts\Cache\CacheInterface; @@ -17,6 +20,7 @@ class ProcessDownloader implements DownloaderInterface * @var RedisAdapter $cache */ public function __construct( + private MessageBusInterface $bus, private EntityManagerInterface $entityManager, private MediaFiles $mediaFiles, private CacheInterface $cache, @@ -88,6 +92,12 @@ class ProcessDownloader implements DownloaderInterface } } catch (ProcessFailedException $exception) { $downloadEntity->setStatus('Failed'); + $this->bus->dispatch(new AddEventLogCommand( + $downloadEntity->getUser()->getId(), + DownloadEvents::DOWNLOAD_ERROR->type(), + DownloadEvents::DOWNLOAD_ERROR->message() . ': ' . $exception->getMessage(), + (array) $downloadEntity + )); } $this->entityManager->flush(); diff --git a/src/Download/Framework/Controller/ApiController.php b/src/Download/Framework/Controller/ApiController.php index 962053b..c3591fa 100644 --- a/src/Download/Framework/Controller/ApiController.php +++ b/src/Download/Framework/Controller/ApiController.php @@ -11,7 +11,9 @@ use App\Download\Action\Input\DownloadMediaInput; use App\Download\Action\Input\DownloadSeasonInput; use App\Download\Action\Input\PauseDownloadInput; use App\Download\Action\Input\ResumeDownloadInput; +use App\Download\DownloadEvents; use App\Download\Framework\Repository\DownloadRepository; +use App\EventLog\Action\Command\AddEventLogCommand; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Messenger\MessageBusInterface; @@ -41,6 +43,13 @@ class ApiController extends AbstractController $input->downloadId = $download->getId(); $input->userId = $this->getUser()->getId(); + $this->bus->dispatch(new AddEventLogCommand( + $this->getUser(), + DownloadEvents::DOWNLOAD_ADDED->type(), + DownloadEvents::DOWNLOAD_ADDED->message(), + (array) $download + )); + try { $this->bus->dispatch($input->toCommand()); } catch (\Throwable $exception) { diff --git a/src/EventLog/Action/Command/AddEventLogCommand.php b/src/EventLog/Action/Command/AddEventLogCommand.php index 55baea1..4d7be37 100644 --- a/src/EventLog/Action/Command/AddEventLogCommand.php +++ b/src/EventLog/Action/Command/AddEventLogCommand.php @@ -3,11 +3,13 @@ namespace App\EventLog\Action\Command; use OneToMany\RichBundle\Contract\CommandInterface; +use Symfony\Component\Security\Core\User\UserInterface; /** @implements CommandInterface */ class AddEventLogCommand implements CommandInterface { public function __construct( + public UserInterface $user, public string $type, public string $message, public array $context, diff --git a/src/EventLog/Action/Handler/AddEventLogHandler.php b/src/EventLog/Action/Handler/AddEventLogHandler.php index f90cf0a..3e9066f 100644 --- a/src/EventLog/Action/Handler/AddEventLogHandler.php +++ b/src/EventLog/Action/Handler/AddEventLogHandler.php @@ -19,6 +19,7 @@ class AddEventLogHandler implements HandlerInterface public function handle(CommandInterface $command): ResultInterface { $eventLog = $this->repository->insert( + user: $command->user, type: $command->type, message: $command->message, context: $command->context diff --git a/src/EventLog/Framework/Entity/EventLog.php b/src/EventLog/Framework/Entity/EventLog.php index 210f35c..734d587 100644 --- a/src/EventLog/Framework/Entity/EventLog.php +++ b/src/EventLog/Framework/Entity/EventLog.php @@ -3,6 +3,7 @@ namespace App\EventLog\Framework\Entity; use App\EventLog\Framework\Repository\EventLogRepository; +use App\User\Framework\Entity\User; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; @@ -23,6 +24,9 @@ class EventLog #[ORM\Column(type: Types::JSON, nullable: true)] private ?array $context = null; + #[ORM\ManyToOne(inversedBy: 'eventLogs')] + private ?User $user = null; + public function getId(): ?int { return $this->id; @@ -60,4 +64,16 @@ class EventLog $this->context = $context; return $this; } + + public function getUser(): ?User + { + return $this->user; + } + + public function setUser(?User $user): static + { + $this->user = $user; + + return $this; + } } diff --git a/src/EventLog/Framework/Repository/EventLogRepository.php b/src/EventLog/Framework/Repository/EventLogRepository.php index fb0ecf5..848445c 100644 --- a/src/EventLog/Framework/Repository/EventLogRepository.php +++ b/src/EventLog/Framework/Repository/EventLogRepository.php @@ -3,6 +3,7 @@ namespace App\EventLog\Framework\Repository; use App\EventLog\Framework\Entity\EventLog; +use App\User\Framework\Entity\User; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Persistence\ManagerRegistry; @@ -17,11 +18,13 @@ class EventLogRepository extends ServiceEntityRepository } public function insert( + User $user, string $type, string $message, array $context = [] ): EventLog { $eventLog = new EventLog() + ->setUser($user) ->setType($type) ->setMessage($message) ->setContext($context);