This commit is contained in:
thibaud-leclere
2026-01-13 21:26:00 +01:00
parent d4a3d32e0a
commit e5d5fe4343
14 changed files with 2373 additions and 2169 deletions

1
.env
View File

@@ -48,3 +48,4 @@ MAILER_DSN=null://null
###< symfony/mailer ### ###< symfony/mailer ###
TMDB_API_TOKEN=eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJkZmE5NDZmMTZmMTcyYmNlMzk0MzZiZmVhZDc2ZTk3NCIsIm5iZiI6MTY0OTE4MjMyNS43NTAwMDAyLCJzdWIiOiI2MjRjODY3NWFmNThjYjAwNTE1NzZiYmEiLCJzY29wZXMiOlsiYXBpX3JlYWQiXSwidmVyc2lvbiI6MX0.KE68nNxPGYWr5WHVaUuILMOH3sPhiAc9CucPVTgRPpM TMDB_API_TOKEN=eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJkZmE5NDZmMTZmMTcyYmNlMzk0MzZiZmVhZDc2ZTk3NCIsIm5iZiI6MTY0OTE4MjMyNS43NTAwMDAyLCJzdWIiOiI2MjRjODY3NWFmNThjYjAwNTE1NzZiYmEiLCJzY29wZXMiOlsiYXBpX3JlYWQiXSwidmVyc2lvbiI6MX0.KE68nNxPGYWr5WHVaUuILMOH3sPhiAc9CucPVTgRPpM
TMDB_HOST=https://api.themoviedb.org/3

View File

@@ -1,6 +1,7 @@
<component name="ProjectDictionaryState"> <component name="ProjectDictionaryState">
<dictionary name="project"> <dictionary name="project">
<words> <words>
<w>Letterboxd</w>
<w>tmdb</w> <w>tmdb</w>
</words> </words>
</dictionary> </dictionary>

8
.idea/laravel-idea.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="InertiaPackage">
<option name="directoryPaths">
<list />
</option>
</component>
</project>

2
.idea/php.xml generated
View File

@@ -139,7 +139,7 @@
<path value="$PROJECT_DIR$/vendor/symfony/twig-bridge" /> <path value="$PROJECT_DIR$/vendor/symfony/twig-bridge" />
</include_path> </include_path>
</component> </component>
<component name="PhpProjectSharedConfiguration" php_language_level="8.5"> <component name="PhpProjectSharedConfiguration" php_language_level="8.4">
<option name="suggestChangeDefaultLanguageLevel" value="false" /> <option name="suggestChangeDefaultLanguageLevel" value="false" />
</component> </component>
<component name="PhpStanOptionsConfiguration"> <component name="PhpStanOptionsConfiguration">

6
.idea/symfony2.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Symfony2PluginSettings">
<option name="pluginEnabled" value="true" />
</component>
</project>

View File

@@ -14,6 +14,7 @@
"phpstan/phpdoc-parser": "^2.3", "phpstan/phpdoc-parser": "^2.3",
"symfony/asset": "8.0.*", "symfony/asset": "8.0.*",
"symfony/asset-mapper": "8.0.*", "symfony/asset-mapper": "8.0.*",
"symfony/cache": "8.0.*",
"symfony/console": "8.0.*", "symfony/console": "8.0.*",
"symfony/doctrine-messenger": "8.0.*", "symfony/doctrine-messenger": "8.0.*",
"symfony/dotenv": "8.0.*", "symfony/dotenv": "8.0.*",

4297
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,33 @@
<?php
namespace App\Context\TMDB;
abstract class AbstractSearchContext
{
public function __construct(
protected int $page,
protected array $results,
protected int $total_pages,
protected int $total_results,
) {}
public function getPage(): int
{
return $this->page;
}
public function getResults(): array
{
return $this->results;
}
public function getTotalPages(): int
{
return $this->total_pages;
}
public function getTotalResults(): int
{
return $this->total_results;
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace App\Context\TMDB;
use App\Model\TMDB\TMDBMovie;
class MovieSearchContext extends AbstractSearchContext
{
/**
* @var TMDBMovie[]
*/
protected array $results;
}

View File

@@ -5,26 +5,32 @@ declare(strict_types=1);
namespace App\Controller; namespace App\Controller;
use App\Gateway\TMDBGateway; use App\Gateway\TMDBGateway;
use App\Model\Ltbxd\LtbxdMovie;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpClient\Exception\ClientException; use Symfony\Component\HttpClient\Exception\ClientException;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Serializer\SerializerInterface;
class HomepageController extends AbstractController class HomepageController extends AbstractController
{ {
public function __construct( public function __construct(
private readonly TMDBGateway $TMDBGateway, private readonly TMDBGateway $TMDBGateway, private readonly SerializerInterface $serializer,
) { ) {}
}
#[Route('/')] #[Route('/')]
public function index(): Response public function index(SerializerInterface $serializer): Response
{ {
try { $file = file_get_contents('files/watched.csv');
dd($this->TMDBGateway->searchMovie('The Batman')); $ltbxdMovies = $this->serializer->deserialize($file, LtbxdMovie::class.'[]', 'csv');
} catch (ClientException $e) { /** @var LtbxdMovie $ltbxdMovie */
dd($e->getResponse()->getInfo()); $films = [];
foreach ($ltbxdMovies as $ltbxdMovie) {
// Search movie on TMDB
$searchResult = $this->TMDBGateway->searchMovie($ltbxdMovie->getName());
$films[] = $searchResult->getResults()[0];
} }
dd($films);
return $this->render('homepage/index.html.twig'); return $this->render('homepage/index.html.twig');
} }

View File

@@ -2,33 +2,43 @@
namespace App\Gateway; namespace App\Gateway;
use App\Context\TMDB\MovieSearchContext;
use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Cache\ItemInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface; use Symfony\Contracts\HttpClient\HttpClientInterface;
use function Symfony\Component\String\u;
class TMDBGateway class TMDBGateway
{ {
private const TMDB_URL = 'https://api.themoviedb.org/3'; private const string SEARCH_URI = '/search/movie';
private const SEARCH_URI = '/search/movie';
public function __construct( public function __construct(
private readonly HttpClientInterface $client, private readonly HttpClientInterface $client,
private readonly SerializerInterface $serializer,
private readonly CacheInterface $cache,
#[Autowire('%env(TMDB_API_TOKEN)%')] #[Autowire('%env(TMDB_API_TOKEN)%')]
private readonly string $apiToken, private readonly string $apiToken,
#[Autowire('%env(TMDB_HOST)%')]
private readonly string $host,
) { ) {
} }
public function findMovie(string $movieName) public function searchMovie(string $movieName): ?MovieSearchContext
{ {
$url = self::TMDB_URL.self::SEARCH_URI.'?'.http_build_query(['query' => $movieName]); $cacheKey = 'tmdb_movie.'.u($movieName)->snake();
try { return $this->cache->get($cacheKey, function (ItemInterface $item) use ($movieName) {
$response = $this->client->request('GET', $url, ['headers' => $this->getHeaders()]); $url = $this->host.self::SEARCH_URI.'?'.http_build_query(['query' => $movieName]);
$result = $response->getContent(); try {
} catch (\Throwable) { $response = $this->client->request('GET', $url, ['headers' => $this->getHeaders()]);
return null; $result = $response->getContent();
} return $this->serializer->deserialize($result, MovieSearchContext::class, 'json');
} catch (\Throwable) {
return json_decode($this->client->request('GET', $url, ['headers' => $this->getHeaders()])->getContent(), associative: true); return null;
}
});
} }
private function getHeaders(): array private function getHeaders(): array

View File

@@ -0,0 +1,39 @@
<?php
namespace App\Model\Ltbxd;
use Symfony\Component\Serializer\Attribute\SerializedName;
class LtbxdMovie
{
public function __construct(
#[SerializedName('Date')]
private readonly \DateTime $date,
#[SerializedName('Name')]
private readonly string $name,
#[SerializedName('Year')]
private readonly int $year,
#[SerializedName('Letterboxd URI')]
private readonly string $ltbxdUri,
) {}
public function getDate(): \DateTime
{
return $this->date;
}
public function getName(): string
{
return $this->name;
}
public function getYear(): int
{
return $this->year;
}
public function getLtbxdUri(): string
{
return $this->ltbxdUri;
}
}

View File

@@ -4,5 +4,90 @@ namespace App\Model\TMDB;
class TMDBMovie class TMDBMovie
{ {
public function __construct(
private readonly bool $adult,
private readonly ?string $backdrop_path,
private readonly array $genre_ids,
private readonly int $id,
private readonly string $original_language,
private readonly string $original_title,
private readonly string $overview,
private readonly float $popularity,
private readonly ?string $poster_path,
private readonly \DateTime|string $release_date,
private readonly string $title,
private readonly bool $video,
private readonly float $vote_average,
private readonly int $vote_count,
) {}
public function isAdult(): bool
{
return $this->adult;
}
public function getBackdropPath(): ?string
{
return $this->backdrop_path;
}
public function getGenreIds(): array
{
return $this->genre_ids;
}
public function getId(): int
{
return $this->id;
}
public function getOriginalLanguage(): string
{
return $this->original_language;
}
public function getOriginalTitle(): string
{
return $this->original_title;
}
public function getOverview(): string
{
return $this->overview;
}
public function getPopularity(): float
{
return $this->popularity;
}
public function getPosterPath(): ?string
{
return $this->poster_path;
}
public function getReleaseDate(): \DateTime|string
{
return $this->release_date;
}
public function getTitle(): string
{
return $this->title;
}
public function isVideo(): bool
{
return $this->video;
}
public function getVoteAverage(): float
{
return $this->vote_average;
}
public function getVoteCount(): int
{
return $this->vote_count;
}
} }