From 5c35aff23bce54474f7c6dd87b8eeaebb0718fde Mon Sep 17 00:00:00 2001 From: thibaud-leclere Date: Wed, 14 Jan 2026 00:54:49 +0100 Subject: [PATCH] Add db, sync movies command --- .env | 9 +++- .idea/dataSources.xml | 12 +++++ compose.yaml | 4 +- config/packages/doctrine.yaml | 15 ++++--- migrations/Version20260113232805.php | 34 ++++++++++++++ src/Command/SyncMoviesCommand.php | 63 ++++++++++++++++++++++++++ src/Controller/HomepageController.php | 22 ++++----- src/Entity/Movie.php | 65 +++++++++++++++++++++++++++ src/Gateway/TMDBGateway.php | 24 +++++----- src/Model/Ltbxd/LtbxdMovie.php | 5 +++ src/Repository/MovieRepository.php | 43 ++++++++++++++++++ 11 files changed, 264 insertions(+), 32 deletions(-) create mode 100644 .idea/dataSources.xml create mode 100644 migrations/Version20260113232805.php create mode 100644 src/Command/SyncMoviesCommand.php create mode 100644 src/Entity/Movie.php create mode 100644 src/Repository/MovieRepository.php diff --git a/.env b/.env index dec1f4d..654c661 100644 --- a/.env +++ b/.env @@ -33,7 +33,14 @@ DEFAULT_URI=http://localhost # DATABASE_URL="sqlite:///%kernel.project_dir%/var/data_%kernel.environment%.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=16&charset=utf8" +DATABASE_URL="postgresql://app:pwd@127.0.0.1:5432/app?serverVersion=16&charset=utf8" + +POSTGRES_VERSION=16 +POSTGRES_HOST=127.0.0.1 +POSTGRES_PORT=5432 +POSTGRES_DB=app +POSTGRES_USER=app +POSTGRES_PASSWORD=pwd ###< doctrine/doctrine-bundle ### ###> symfony/messenger ### diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000..a8f0914 --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,12 @@ + + + + + postgresql + true + org.postgresql.Driver + jdbc:postgresql://localhost:5432/app + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/compose.yaml b/compose.yaml index 89c74d1..9d86ef4 100644 --- a/compose.yaml +++ b/compose.yaml @@ -6,7 +6,7 @@ services: environment: POSTGRES_DB: ${POSTGRES_DB:-app} # You should definitely change the password in production - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-!ChangeMe!} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-pwd} POSTGRES_USER: ${POSTGRES_USER:-app} healthcheck: test: ["CMD", "pg_isready", "-d", "${POSTGRES_DB:-app}", "-U", "${POSTGRES_USER:-app}"] @@ -17,6 +17,8 @@ services: - database_data:/var/lib/postgresql/data:rw # You may use a bind-mounted host directory instead, so that it is harder to accidentally remove the volume and lose all your data! # - ./docker/db/data:/var/lib/postgresql/data:rw + ports: + - "0.0.0.0:5432:5432" ###< doctrine/doctrine-bundle ### volumes: diff --git a/config/packages/doctrine.yaml b/config/packages/doctrine.yaml index 6c57caf..fedac47 100644 --- a/config/packages/doctrine.yaml +++ b/config/packages/doctrine.yaml @@ -1,6 +1,13 @@ doctrine: dbal: - url: '%env(resolve:DATABASE_URL)%' + driver: pdo_pgsql + host: '%env(resolve:POSTGRES_HOST)%' + port: '%env(resolve:POSTGRES_PORT)%' + dbname: '%env(resolve:POSTGRES_DB)%' + user: '%env(resolve:POSTGRES_USER)%' + password: '%env(resolve:POSTGRES_PASSWORD)%' + server_version: '%env(resolve:POSTGRES_VERSION)%' + charset: utf8 # IMPORTANT: You MUST configure your server version, # either here or in the DATABASE_URL env var (see .env file) @@ -23,12 +30,6 @@ doctrine: controller_resolver: auto_mapping: false -when@test: - doctrine: - dbal: - # "TEST_TOKEN" is typically set by ParaTest - dbname_suffix: '_test%env(default::TEST_TOKEN)%' - when@prod: doctrine: orm: diff --git a/migrations/Version20260113232805.php b/migrations/Version20260113232805.php new file mode 100644 index 0000000..7b4e198 --- /dev/null +++ b/migrations/Version20260113232805.php @@ -0,0 +1,34 @@ +addSql('CREATE TABLE movie (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, tmdb_id INT NOT NULL, ltbxd_ref VARCHAR(255) NOT NULL, title VARCHAR(255) NOT NULL, PRIMARY KEY (id))'); + $this->addSql('CREATE TABLE messenger_messages (id BIGINT GENERATED BY DEFAULT AS IDENTITY 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_75EA56E0FB7336F0E3BD61CE16BA31DBBF396750 ON messenger_messages (queue_name, available_at, delivered_at, id)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('DROP TABLE movie'); + $this->addSql('DROP TABLE messenger_messages'); + } +} diff --git a/src/Command/SyncMoviesCommand.php b/src/Command/SyncMoviesCommand.php new file mode 100644 index 0000000..ec7accf --- /dev/null +++ b/src/Command/SyncMoviesCommand.php @@ -0,0 +1,63 @@ +serializer->deserialize($file, LtbxdMovie::class.'[]', 'csv'); + } catch (ExceptionInterface $e) { + $output->writeln($e->getMessage()); + return Command::FAILURE; + } + + $i = 0; + /** @var LtbxdMovie $ltbxdMovie */ + foreach ($ltbxdMovies as $ltbxdMovie) { + // If the movie already exists, skip + if (0 < $this->em->getRepository(Movie::class)->count(['ltbxdRef' => $ltbxdMovie->getLtbxdRef()])) { + continue; + } + + // Search movie on TMDB + $film = $this->TMDBGateway->searchMovie($ltbxdMovie->getName()); + if ($film) { + $output->writeln('* Found '.$ltbxdMovie->getName()); + + $filmEntity = new Movie() + ->setLtbxdRef($ltbxdMovie->getLtbxdRef()) + ->setTitle($ltbxdMovie->getName()) + ->setTmdbId($film->getId()) + ; + $this->em->persist($filmEntity); + ++$i; + } + + if (0 === $i % 50) { + $this->em->flush(); + } + } + + return Command::SUCCESS; + } +} diff --git a/src/Controller/HomepageController.php b/src/Controller/HomepageController.php index f64951a..eef8176 100644 --- a/src/Controller/HomepageController.php +++ b/src/Controller/HomepageController.php @@ -7,7 +7,6 @@ namespace App\Controller; use App\Gateway\TMDBGateway; use App\Model\Ltbxd\LtbxdMovie; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -use Symfony\Component\HttpClient\Exception\ClientException; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Serializer\SerializerInterface; @@ -21,16 +20,17 @@ class HomepageController extends AbstractController #[Route('/')] public function index(SerializerInterface $serializer): Response { - $file = file_get_contents('files/watched.csv'); - $ltbxdMovies = $this->serializer->deserialize($file, LtbxdMovie::class.'[]', 'csv'); - /** @var LtbxdMovie $ltbxdMovie */ - $films = []; - foreach ($ltbxdMovies as $ltbxdMovie) { - // Search movie on TMDB - $searchResult = $this->TMDBGateway->searchMovie($ltbxdMovie->getName()); - $films[] = $searchResult->getResults()[0]; - } - dd($films); +// $file = file_get_contents('files/watched.csv'); +// $ltbxdMovies = $this->serializer->deserialize($file, LtbxdMovie::class.'[]', 'csv'); +// /** @var LtbxdMovie $ltbxdMovie */ +// $films = []; +// foreach ($ltbxdMovies as $ltbxdMovie) { +// // Search movie on TMDB +// $film = $this->TMDBGateway->searchMovie($ltbxdMovie->getName()); +// if ($film) { +// $films[] = $film; +// } +// } return $this->render('homepage/index.html.twig'); } diff --git a/src/Entity/Movie.php b/src/Entity/Movie.php new file mode 100644 index 0000000..4853b18 --- /dev/null +++ b/src/Entity/Movie.php @@ -0,0 +1,65 @@ +id; + } + + public function getTmdbId(): ?int + { + return $this->tmdbId; + } + + public function setTmdbId(int $tmdbId): static + { + $this->tmdbId = $tmdbId; + + return $this; + } + + public function getLtbxdRef(): ?string + { + return $this->ltbxdRef; + } + + public function setLtbxdRef(string $ltbxdRef): static + { + $this->ltbxdRef = $ltbxdRef; + + return $this; + } + + public function getTitle(): ?string + { + return $this->title; + } + + public function setTitle(string $title): static + { + $this->title = $title; + + return $this; + } +} diff --git a/src/Gateway/TMDBGateway.php b/src/Gateway/TMDBGateway.php index 672ee99..4fd930f 100644 --- a/src/Gateway/TMDBGateway.php +++ b/src/Gateway/TMDBGateway.php @@ -3,6 +3,7 @@ namespace App\Gateway; use App\Context\TMDB\MovieSearchContext; +use App\Model\TMDB\TMDBMovie; use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\Serializer\SerializerInterface; use Symfony\Contracts\Cache\CacheInterface; @@ -17,7 +18,6 @@ class TMDBGateway public function __construct( private readonly HttpClientInterface $client, private readonly SerializerInterface $serializer, - private readonly CacheInterface $cache, #[Autowire('%env(TMDB_API_TOKEN)%')] private readonly string $apiToken, #[Autowire('%env(TMDB_HOST)%')] @@ -25,20 +25,20 @@ class TMDBGateway ) { } - public function searchMovie(string $movieName): ?MovieSearchContext + public function searchMovie(string $movieName): ?TMDBMovie { - $cacheKey = 'tmdb_movie.'.u($movieName)->snake(); - - return $this->cache->get($cacheKey, function (ItemInterface $item) use ($movieName) { - $url = $this->host.self::SEARCH_URI.'?'.http_build_query(['query' => $movieName]); - try { - $response = $this->client->request('GET', $url, ['headers' => $this->getHeaders()]); - $result = $response->getContent(); - return $this->serializer->deserialize($result, MovieSearchContext::class, 'json'); - } catch (\Throwable) { + $url = $this->host.self::SEARCH_URI.'?'.http_build_query(['query' => $movieName]); + try { + $response = $this->client->request('GET', $url, ['headers' => $this->getHeaders()]); + $result = $response->getContent(); + $searchContext = $this->serializer->deserialize($result, MovieSearchContext::class, 'json'); + if (empty($searchResult = $searchContext->getResults())) { return null; } - }); + return reset($searchResult); + } catch (\Throwable) { + return null; + } } private function getHeaders(): array diff --git a/src/Model/Ltbxd/LtbxdMovie.php b/src/Model/Ltbxd/LtbxdMovie.php index a94965f..4007cb1 100644 --- a/src/Model/Ltbxd/LtbxdMovie.php +++ b/src/Model/Ltbxd/LtbxdMovie.php @@ -36,4 +36,9 @@ class LtbxdMovie { return $this->ltbxdUri; } + + public function getLtbxdRef(): string + { + return basename($this->ltbxdUri); + } } diff --git a/src/Repository/MovieRepository.php b/src/Repository/MovieRepository.php new file mode 100644 index 0000000..6f6f2a9 --- /dev/null +++ b/src/Repository/MovieRepository.php @@ -0,0 +1,43 @@ + + */ +class MovieRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Movie::class); + } + + // /** + // * @return Movie[] Returns an array of Movie objects + // */ + // public function findByExampleField($value): array + // { + // return $this->createQueryBuilder('m') + // ->andWhere('m.exampleField = :val') + // ->setParameter('val', $value) + // ->orderBy('m.id', 'ASC') + // ->setMaxResults(10) + // ->getQuery() + // ->getResult() + // ; + // } + + // public function findOneBySomeField($value): ?Movie + // { + // return $this->createQueryBuilder('m') + // ->andWhere('m.exampleField = :val') + // ->setParameter('val', $value) + // ->getQuery() + // ->getOneOrNullResult() + // ; + // } +}