feat: search results

This commit is contained in:
2025-04-20 23:47:12 -05:00
parent a4ad43cfe0
commit f5552e3ad7
20 changed files with 796 additions and 113 deletions

View File

@@ -1,4 +1,7 @@
DATABASE_URL="mysql://root:password@database:3306/app?serverVersion=10.6.19.2-MariaDB&charset=utf8mb4"
###> symfony/framework-bundle ###
APP_SECRET=70169beadfbc8101c393cbfbba27a313
###< symfony/framework-bundle ###
DOWNLOAD_DIR=./movies
MERCURE_URL=http://mercure/.well-known/mercure
MERCURE_PUBLIC_URL=https://dev.caldwell.digital/hub/.well-known/mercure
MERCURE_JWT_SECRET="!ChangeThisMercureHubJWTSecretKey!"

View File

@@ -8,6 +8,8 @@
"ext-ctype": "*",
"ext-iconv": "*",
"1tomany/rich-bundle": "^1.8",
"nyholm/psr7": "*",
"php-tmdb/api": "^4.1",
"symfony/asset": "7.2.*",
"symfony/console": "7.2.*",
"symfony/dotenv": "7.2.*",

544
composer.lock generated
View File

@@ -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": "13701d11cfdac6c3280250a7a031a679",
"content-hash": "52ea99b6f072a3dba10316c24060b886",
"packages": [
{
"name": "1tomany/data-uri",
@@ -251,6 +251,84 @@
},
"time": "2025-04-07T20:06:18+00:00"
},
{
"name": "nyholm/psr7",
"version": "1.8.2",
"source": {
"type": "git",
"url": "https://github.com/Nyholm/psr7.git",
"reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Nyholm/psr7/zipball/a71f2b11690f4b24d099d6b16690a90ae14fc6f3",
"reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3",
"shasum": ""
},
"require": {
"php": ">=7.2",
"psr/http-factory": "^1.0",
"psr/http-message": "^1.1 || ^2.0"
},
"provide": {
"php-http/message-factory-implementation": "1.0",
"psr/http-factory-implementation": "1.0",
"psr/http-message-implementation": "1.0"
},
"require-dev": {
"http-interop/http-factory-tests": "^0.9",
"php-http/message-factory": "^1.0",
"php-http/psr7-integration-tests": "^1.0",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.4",
"symfony/error-handler": "^4.4"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.8-dev"
}
},
"autoload": {
"psr-4": {
"Nyholm\\Psr7\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Tobias Nyholm",
"email": "tobias.nyholm@gmail.com"
},
{
"name": "Martijn van der Ven",
"email": "martijn@vanderven.se"
}
],
"description": "A fast PHP7 implementation of PSR-7",
"homepage": "https://tnyholm.se",
"keywords": [
"psr-17",
"psr-7"
],
"support": {
"issues": "https://github.com/Nyholm/psr7/issues",
"source": "https://github.com/Nyholm/psr7/tree/1.8.2"
},
"funding": [
{
"url": "https://github.com/Zegnat",
"type": "github"
},
{
"url": "https://github.com/nyholm",
"type": "github"
}
],
"time": "2024-09-09T07:06:30+00:00"
},
{
"name": "opis/json-schema",
"version": "2.4.1",
@@ -441,6 +519,192 @@
},
"time": "2021-05-22T15:57:08+00:00"
},
{
"name": "php-http/discovery",
"version": "1.20.0",
"source": {
"type": "git",
"url": "https://github.com/php-http/discovery.git",
"reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-http/discovery/zipball/82fe4c73ef3363caed49ff8dd1539ba06044910d",
"reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d",
"shasum": ""
},
"require": {
"composer-plugin-api": "^1.0|^2.0",
"php": "^7.1 || ^8.0"
},
"conflict": {
"nyholm/psr7": "<1.0",
"zendframework/zend-diactoros": "*"
},
"provide": {
"php-http/async-client-implementation": "*",
"php-http/client-implementation": "*",
"psr/http-client-implementation": "*",
"psr/http-factory-implementation": "*",
"psr/http-message-implementation": "*"
},
"require-dev": {
"composer/composer": "^1.0.2|^2.0",
"graham-campbell/phpspec-skip-example-extension": "^5.0",
"php-http/httplug": "^1.0 || ^2.0",
"php-http/message-factory": "^1.0",
"phpspec/phpspec": "^5.1 || ^6.1 || ^7.3",
"sebastian/comparator": "^3.0.5 || ^4.0.8",
"symfony/phpunit-bridge": "^6.4.4 || ^7.0.1"
},
"type": "composer-plugin",
"extra": {
"class": "Http\\Discovery\\Composer\\Plugin",
"plugin-optional": true
},
"autoload": {
"psr-4": {
"Http\\Discovery\\": "src/"
},
"exclude-from-classmap": [
"src/Composer/Plugin.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Márk Sági-Kazár",
"email": "mark.sagikazar@gmail.com"
}
],
"description": "Finds and installs PSR-7, PSR-17, PSR-18 and HTTPlug implementations",
"homepage": "http://php-http.org",
"keywords": [
"adapter",
"client",
"discovery",
"factory",
"http",
"message",
"psr17",
"psr7"
],
"support": {
"issues": "https://github.com/php-http/discovery/issues",
"source": "https://github.com/php-http/discovery/tree/1.20.0"
},
"time": "2024-10-02T11:20:13+00:00"
},
{
"name": "php-tmdb/api",
"version": "4.1.3",
"source": {
"type": "git",
"url": "https://github.com/php-tmdb/api.git",
"reference": "7f7b3c6317b1346e6bc646ad63edb212a2b55584"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-tmdb/api/zipball/7f7b3c6317b1346e6bc646ad63edb212a2b55584",
"reference": "7f7b3c6317b1346e6bc646ad63edb212a2b55584",
"shasum": ""
},
"require": {
"ext-json": "*",
"php": "^7.3 || ^7.4 || ^8.0 || ^8.1",
"php-http/discovery": "^1.11",
"psr/cache": "^1 || ^2 || ^3",
"psr/event-dispatcher": "^1",
"psr/event-dispatcher-implementation": "^1",
"psr/http-client": "^1",
"psr/http-client-implementation": "^1",
"psr/http-factory": "^1",
"psr/http-factory-implementation": "^1",
"psr/http-message": "^1",
"psr/log": "^1 || ^2 || ^3",
"psr/simple-cache": "^1 || ^2 || ^3",
"symfony/options-resolver": ">=4.4 || ^5 || ^6"
},
"require-dev": {
"jeroen/psr-log-test-doubles": "^2.1 || ^3",
"monolog/monolog": ">=1.11.0",
"nyholm/psr7": "^1.2",
"php-http/cache-plugin": "^1.7",
"php-http/guzzle7-adapter": "^0.1",
"php-http/mock-client": "^1.2",
"phpstan/phpstan": "^1.8.1",
"phpunit/phpunit": ">=8.5.23 || ^9.3",
"slevomat/coding-standard": "^6.4",
"squizlabs/php_codesniffer": "^3.5.8",
"symfony/cache": ">=4.4 || ^5 || ^6",
"symfony/event-dispatcher": ">=4.4 || ^5 || ^6",
"vimeo/psalm": "^4"
},
"suggest": {
"monolog/monolog": "Great logger to use, but you can pick any PSR-3 logger you wish.",
"php-http/cache-plugin": "When making use of cache, you need to install this plugin.",
"psr/cache-implementation": "If you wish to enable caching features, provide an PSR-6 cache implementation.",
"psr/log-implementation": "If you wish to enable logging features, provide an PSR-3 logger.",
"psr/simple-cache-implementation": "If you wish to enable caching features, provide an PSR-16 cache.",
"symfony/cache": "Great cache to use, but you can pick any PSR-6 cache you wish."
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "4.0-dev"
}
},
"autoload": {
"psr-4": {
"Tmdb\\": "lib/Tmdb"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Roterman",
"email": "michael@wtfz.net",
"homepage": "http://wtfz.net"
}
],
"description": "PHP wrapper for TMDB (TheMovieDatabase) API v3. Supports two types of approaches, one modelled with repositories, models and factories. And the other by simple array access to RAW data from The Movie Database.",
"homepage": "https://github.com/php-tmdb/api",
"keywords": [
"api",
"movie",
"php",
"tmdb",
"tv",
"tv show",
"tvdb",
"wrapper"
],
"support": {
"issues": "https://github.com/php-tmdb/api/issues",
"source": "https://github.com/php-tmdb/api/tree/4.1.3"
},
"funding": [
{
"url": "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=SMLZ362KQ8K8W",
"type": "custom"
},
{
"url": "https://github.com/php-tmdb",
"type": "github"
},
{
"url": "https://github.com/wtfzdotnet",
"type": "github"
}
],
"time": "2022-11-04T19:25:12+00:00"
},
{
"name": "phpdocumentor/reflection-common",
"version": "2.2.0",
@@ -863,6 +1127,166 @@
},
"time": "2019-01-08T18:20:26+00:00"
},
{
"name": "psr/http-client",
"version": "1.0.3",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-client.git",
"reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90",
"reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90",
"shasum": ""
},
"require": {
"php": "^7.0 || ^8.0",
"psr/http-message": "^1.0 || ^2.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Client\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for HTTP clients",
"homepage": "https://github.com/php-fig/http-client",
"keywords": [
"http",
"http-client",
"psr",
"psr-18"
],
"support": {
"source": "https://github.com/php-fig/http-client"
},
"time": "2023-09-23T14:17:50+00:00"
},
{
"name": "psr/http-factory",
"version": "1.1.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-factory.git",
"reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
"reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
"shasum": ""
},
"require": {
"php": ">=7.1",
"psr/http-message": "^1.0 || ^2.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Message\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "PSR-17: Common interfaces for PSR-7 HTTP message factories",
"keywords": [
"factory",
"http",
"message",
"psr",
"psr-17",
"psr-7",
"request",
"response"
],
"support": {
"source": "https://github.com/php-fig/http-factory"
},
"time": "2024-04-15T12:06:14+00:00"
},
{
"name": "psr/http-message",
"version": "1.1",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-message.git",
"reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
"reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.1.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Message\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interface for HTTP messages",
"homepage": "https://github.com/php-fig/http-message",
"keywords": [
"http",
"http-message",
"psr",
"psr-7",
"request",
"response"
],
"support": {
"source": "https://github.com/php-fig/http-message/tree/1.1"
},
"time": "2023-04-04T09:50:52+00:00"
},
{
"name": "psr/log",
"version": "3.0.2",
@@ -913,6 +1337,57 @@
},
"time": "2024-09-11T13:17:53+00:00"
},
{
"name": "psr/simple-cache",
"version": "3.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/simple-cache.git",
"reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865",
"reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865",
"shasum": ""
},
"require": {
"php": ">=8.0.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\SimpleCache\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interfaces for simple caching",
"keywords": [
"cache",
"caching",
"psr",
"psr-16",
"simple-cache"
],
"support": {
"source": "https://github.com/php-fig/simple-cache/tree/3.0.0"
},
"time": "2021-10-29T13:26:27+00:00"
},
{
"name": "symfony/asset",
"version": "v7.2.0",
@@ -2642,6 +3117,73 @@
],
"time": "2025-03-28T13:32:50+00:00"
},
{
"name": "symfony/options-resolver",
"version": "v7.2.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/options-resolver.git",
"reference": "7da8fbac9dcfef75ffc212235d76b2754ce0cf50"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/options-resolver/zipball/7da8fbac9dcfef75ffc212235d76b2754ce0cf50",
"reference": "7da8fbac9dcfef75ffc212235d76b2754ce0cf50",
"shasum": ""
},
"require": {
"php": ">=8.2",
"symfony/deprecation-contracts": "^2.5|^3"
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\OptionsResolver\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Provides an improved replacement for the array_replace PHP function",
"homepage": "https://symfony.com",
"keywords": [
"config",
"configuration",
"options"
],
"support": {
"source": "https://github.com/symfony/options-resolver/tree/v7.2.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": "2024-11-20T11:17:29+00:00"
},
{
"name": "symfony/password-hasher",
"version": "v7.2.0",

View File

@@ -0,0 +1,10 @@
services:
Psr\Http\Message\RequestFactoryInterface: '@http_discovery.psr17_factory'
Psr\Http\Message\ResponseFactoryInterface: '@http_discovery.psr17_factory'
Psr\Http\Message\ServerRequestFactoryInterface: '@http_discovery.psr17_factory'
Psr\Http\Message\StreamFactoryInterface: '@http_discovery.psr17_factory'
Psr\Http\Message\UploadedFileFactoryInterface: '@http_discovery.psr17_factory'
Psr\Http\Message\UriFactoryInterface: '@http_discovery.psr17_factory'
http_discovery.psr17_factory:
class: Http\Discovery\Psr17Factory

View File

@@ -3,10 +3,14 @@ controllersIndex:
path: ../src/Controller/
namespace: App\Controller
type: attribute
defaults:
schemes: [ https ]
controllersSearch:
resource:
path: ../src/Search/Framework/Controller
namespace: App\Search\Framework\Controller
type: attribute
defaults:
schemes: [ https ]

View File

@@ -2,7 +2,8 @@
namespace App\Enum;
enum MediaType
enum MediaType: string
{
case Movie = 'movies';
case TvShow = 'tvshows';
}

View File

@@ -2,7 +2,15 @@
namespace App\Search\Action\Command;
class SearchCommand
{
use OneToMany\RichBundle\Contract\CommandInterface;
}
class SearchCommand implements CommandInterface
{
/**
* @param string $term
* @implements CommandInterface<SearchCommand>
*/
public function __construct(
public string $term
) {}
}

View File

@@ -2,7 +2,24 @@
namespace App\Search\Action\Handler;
class SearchHandler
{
use App\Search\Action\Result\SearchResult;
use App\Tmdb\Tmdb;
use OneToMany\RichBundle\Contract\CommandInterface;
use OneToMany\RichBundle\Contract\HandlerInterface;
use OneToMany\RichBundle\Contract\ResultInterface;
}
class SearchHandler implements HandlerInterface
{
public function __construct(
private Tmdb $tmdb,
) {}
/*** @implements HandlerInterface<SearchResult> */
public function handle(CommandInterface $command): ResultInterface
{
return new SearchResult(
term: $command->term,
results: $this->tmdb->search($command->term)
);
}
}

View File

@@ -2,7 +2,25 @@
namespace App\Search\Action\Input;
class SearchInput
{
use App\Search\Action\Command\SearchCommand;
use OneToMany\RichBundle\Attribute\SourceQuery;
use OneToMany\RichBundle\Attribute\SourceRequest;
use OneToMany\RichBundle\Contract\CommandInterface;
use OneToMany\RichBundle\Contract\InputInterface;
}
/**
* @implements InputInterface<SearchCommand>
*/
class SearchInput implements InputInterface
{
public function __construct(
#[SourceQuery('term')]
#[SourceRequest('term')]
public string $term
){}
public function toCommand(): CommandInterface
{
return new SearchCommand($this->term);
}
}

View File

@@ -2,7 +2,12 @@
namespace App\Search\Action\Result;
class SearchResult
{
use OneToMany\RichBundle\Contract\ResultInterface;
}
class SearchResult implements ResultInterface
{
public function __construct(
public string $term = "",
public array $results = []
) {}
}

View File

@@ -2,17 +2,27 @@
namespace App\Search\Framework\Controller;
use App\Search\Action\Handler\SearchHandler;
use App\Search\Action\Input\SearchInput;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
final class SearchController extends AbstractController
{
public function __construct(
private SearchHandler $searchHandler,
) {}
#[Route('/search', name: 'app_search', methods: ['GET'])]
public function index(): Response
public function index(
SearchInput $searchInput,
): Response
{
$results = $this->searchHandler->handle($searchInput->toCommand());
return $this->render('search/results.html.twig', [
'controller_name' => 'SearchController',
'results' => $results,
]);
}
}

View File

@@ -2,7 +2,8 @@
namespace App\Tmdb;
use App\ValueObject\MediaResult;
use App\Enum\MediaType;
use App\Tmdb\TmdbResult;
use App\ValueObject\ResultFactory;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
@@ -31,7 +32,7 @@ use Tmdb\Repository\TvSeasonRepository;
use Tmdb\Token\Api\ApiToken;
use Tmdb\Token\Api\BearerToken;
class Client
class Tmdb
{
protected Client $client;
@@ -178,8 +179,8 @@ class Client
throw new \Exception("A media type must be set when parsing from an array.");
}
function parseTvShow(array $data, string $posterBasePath): MediaResult {
return new MediaResult(
function parseTvShow(array $data, string $posterBasePath): TmdbResult {
return new TmdbResult(
imdbId: $data['external_ids']['imdb_id'],
tvdbId: $data['id'],
title: $data['name'],
@@ -191,8 +192,8 @@ class Client
);
}
function parseMovie(array $data, string $posterBasePath): MediaResult {
return new MediaResult(
function parseMovie(array $data, string $posterBasePath): TmdbResult {
return new TmdbResult(
imdbId: $data['external_ids']['imdb_id'],
tvdbId: $data['id'],
title: $data['title'],
@@ -209,67 +210,80 @@ class Client
return $result;
}
private function parseFromObject($result)
private function parseFromObject($result): TmdbResult
{
$result->mediaType = $result instanceof Movie ? 'movies' : 'tvshows';
$mediaType = $result instanceof Movie ? MediaType::Movie->value : MediaType::TvShow->value;
$tmdbResult = new TmdbResult();
$tmdbResult->mediaType = $mediaType;
$tmdbResult->imdbId = $this->getImdbId($result->getId(), $mediaType);
$tmdbResult->title = $this->getTitle($result, $mediaType);
$tmdbResult->poster = self::POSTER_IMG_PATH . $result->getPosterImage();
$tmdbResult->year = $this->getReleaseDate($result, $mediaType);
$tmdbResult->description = $result->getOverview();
return $tmdbResult;
}
$externalIds = $this->cache->get("tmdb.externalIds.{$result->getId()}", function (ItemInterface $item) use ($result) {
if ($result instanceof Movie) {
$externalIds = $this->movieRepository->getExternalIds($result->getId());
} else {
$externalIds = $this->tvRepository->getExternalIds($result->getId());
}
return $externalIds;
public function getImdbId(string $tmdbId, $mediaType)
{
$externalIds = $this->cache->get("tmdb.externalIds.{$tmdbId}",
function (ItemInterface $item) use ($tmdbId, $mediaType) {
switch (MediaType::tryFrom($mediaType)->value) {
case MediaType::Movie->value:
return $this->movieRepository->getExternalIds($tmdbId);
case MediaType::TvShow->value:
return $this->tvRepository->getExternalIds($tmdbId);
default:
return null;
}
});
$images = $this->cache->get("tmdb.images.{$result->getId()}", function (ItemInterface $item) use ($result) {
if ($result instanceof Movie) {
$images = $this->movieRepository->getImages($result->getId());
} else {
$images = $this->tvRepository->getImages($result->getId());
}
return $images;
if (null === $externalIds) {
return null;
}
return $externalIds->getImdbId() !== "" ? $externalIds->getImdbId() : "null";
}
public function getImages($tmdbId, $mediaType)
{
return $this->cache->get("tmdb.images.{$tmdbId}",
function (ItemInterface $item) use ($tmdbId, $mediaType) {
switch (MediaType::tryFrom($mediaType)->value) {
case MediaType::Movie->value:
return $this->movieRepository->getImages($tmdbId);
case MediaType::TvShow->value:
return $this->tvRepository->getImages($tmdbId);
default:
return null;
}
});
}
if (null !== $externalIds) {
$imdbId = $externalIds->getImdbId() !== "" ? $externalIds->getImdbId() : "null";
if ("movies" === $result->mediaType) {
$result->setImdbId($imdbId);
} else {
$result->imdbId = $imdbId;
$result->title = $result->getName();
}
} else {
if ("movies" === $result->mediaType) {
$result->setImdbId("null");
} else {
$result->imdbId = "null";
}
private function getReleaseDate($result, $mediaType): string
{
switch (MediaType::tryFrom($mediaType)->value) {
case MediaType::Movie->value:
return ($result->getReleaseDate() instanceof \DateTime)
? $result->getReleaseDate()->format('Y')
: $result->getReleaseDate();
case MediaType::TvShow->value:
return ($result->getFirstAirDate() instanceof \DateTime)
? $result->getFirstAirDate()->format('Y')
: $result->getFirstAirDate();
default:
return "";
}
}
if ("movies" === $result->mediaType) {
if ($result->getReleaseDate() instanceof \DateTime) {
$result->year = $result->getReleaseDate()->format("Y");
} else {
$result->year = (new \DateTime($result->getReleaseDate()))->format('Y');
}
} else {
if ($result->getFirstAirDate() instanceof \DateTime) {
$result->year = $result->getFirstAirDate()->format("Y");
} else {
$result->year = (new \DateTime($result->getFirstAirDate()))->format('Y');
}
private function getTitle($result, $mediaType): string
{
switch (MediaType::tryFrom($mediaType)->value) {
case MediaType::Movie->value:
return $result->getTitle();
case MediaType::TvShow->value:
return $result->getName();
default:
return "";
}
/** @var Movie $result */
$result->setExternalIds($externalIds);
$result->setImages($images);
$result->getPosterImage()->setFilePath(
self::POSTER_IMG_PATH . $result->getPosterImage()->getFilePath()
);
return $result;
}
}

View File

@@ -1,17 +1,17 @@
<?php
namespace App\ValueObject;
namespace App\Tmdb;
class MediaResult
class TmdbResult
{
public function __construct(
public string $imdbId,
public string $tvdbId,
public string $title,
public string $poster,
public string $excerpt,
public string $year,
public string $mediaType,
public ?string $imdbId = "",
public ?string $tvdbId = "",
public ?string $title = "",
public ?string $poster = "",
public ?string $description = "",
public ?string $year = "",
public ?string $mediaType = "",
public ?array $episodes = null,
) {}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Twig\Components;
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
#[AsTwigComponent]
final class SearchResult
{
}

View File

@@ -2,6 +2,18 @@
"1tomany/rich-bundle": {
"version": "v1.8.3"
},
"php-http/discovery": {
"version": "1.20",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "1.18",
"ref": "f45b5dd173a27873ab19f5e3180b2f661c21de02"
},
"files": [
"config/packages/http_discovery.yaml"
]
},
"symfony/asset-mapper": {
"version": "7.2",
"recipe": {

View File

@@ -1,7 +1,7 @@
<div{{ attributes }}>
<div class="flex flex-col bg-white border border-gray-200 border-t-4 border-t-orange-500 shadow-2xs rounded-xl dark:bg-slate-600 dark:border-neutral-700 dark:border-t-orange-500 dark:shadow-neutral-700/70">
<div class="p-4 md:p-5">
<h3 class="mb-1 text-lg font-bold text-gray-800 dark:text-white">
<h3 class="mb-4 text-lg font-bold text-gray-800 dark:text-white">
{{ title }}
</h3>

View File

@@ -1,18 +1,21 @@
<div {{ attributes }} class="w-full max-w-sm min-w-[200px]">
<div class="relative">
<input
class="w-full bg-orange-500 rounded-md bg-clip-padding backdrop-filter
backdrop-blur-md bg-opacity-40 placeholder:text-slate-200 text-gray-50
text-sm border border-orange-500 rounded-md pl-3 pr-28 py-2 transition
duration-300 ease focus:outline-none focus:border-orange-400 hover:border-orange-300
shadow-sm focus:shadow"
placeholder="TV Show, Movie..."
/>
<button
class="absolute top-1 right-1 flex items-center rounded bg-green-600 py-1 px-2.5 border border-transparent text-center text-sm text-white transition-all shadow-sm hover:shadow focus:bg-green-700 focus:shadow-none active:bg-green-700 hover:bg-green-700 active:shadow-none disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none"
type="button"
>
Search
</button>
<form action="{{ url('app_search') }}">
<input
name="term"
class="w-full bg-orange-500 rounded-md bg-clip-padding backdrop-filter
backdrop-blur-md bg-opacity-40 placeholder:text-slate-200 text-gray-50
text-sm border border-orange-500 rounded-md pl-3 pr-28 py-2 transition
duration-300 ease focus:outline-none focus:border-orange-400 hover:border-orange-300
shadow-sm focus:shadow"
placeholder="TV Show, Movie..."
/>
<button
class="absolute top-1 right-1 flex items-center rounded bg-green-600 py-1 px-2.5 border border-transparent text-center text-sm text-white transition-all shadow-sm hover:shadow focus:bg-green-700 focus:shadow-none active:bg-green-700 hover:bg-green-700 active:shadow-none disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none"
type="button"
>
Search
</button>
</form>
</div>
</div>

View File

@@ -0,0 +1,16 @@
<div{{ attributes }}>
<div class="p-4 flex flex-row gap-6 bg-orange-500 bg-clip-padding backdrop-filter backdrop-blur-md bg-opacity-60 rounded-md">
<img class="w-24" src="{{ poster }}" />
<div class="w-full flex flex-col">
<h3 class="mb-4 text-xl font-medium leading-tight font-bold text-gray-50">
{{ title }} - {{ year }}
</h3>
<p class="text-gray-50">
{{ description }}
</p>
</div>
<a class="h-9 rounded-md py-1 px-2 bg-green-600 text-gray-50"
href="/results/{{ mediaType }}/{{ imdbId }}/"
>choose</a>
</div>
</div>

View File

@@ -3,9 +3,9 @@
{% block title %}Dashboard &mdash - Torsearch{% endblock %}
{% block body %}
<div class="p-4 flex flex-col grow gap-2">
<div class="p-4 flex flex-col grow gap-4">
<h2 class="mb-2 text-3xl font-bold text-gray-50">Dashboard</h2>
<div class="flex flex-row gap-2">
<div class="flex flex-row gap-4">
<twig:Card title="Active Downloads" class="w-full">
<table class="divide-y divide-gray-200 dark:divide-gray-50 dark:bg-gray-50">
<thead>

View File

@@ -3,16 +3,24 @@
{% block title %}Search Results &mdash - Torsearch{% endblock %}
{% block body %}
<div class="p-4 flex flex-col grow gap-2">
<div class="p-4 flex flex-col grow gap-4">
<h2 class="mb-2 text-3xl font-bold text-gray-50">Search Results</h2>
<div class="flex flex-row gap-2">
<twig:Card title="Popular Movies" contentClass="flex flex-row justify-between w-full">
<twig:Poster imdbId="" title="A Working Man" description="" image="https://image.tmdb.org/t/p/w500/xUkUZ8eOnrOnnJAfusZUqKYZiDu.jpg" year="" />
<twig:Poster imdbId="" title="In the Lost Lands" description="" image="https://image.tmdb.org/t/p/w500/iHf6bXPghWB6gT8kFkL1zo00x6X.jpg" year="" />
<twig:Poster imdbId="" title="A Minecraft Movie" description="" image="https://image.tmdb.org/t/p/w500/yFHHfHcUgGAxziP1C3lLt0q2T4s.jpg" year="" />
<twig:Poster imdbId="" title="G20" description="" image="https://image.tmdb.org/t/p/w500/wv6oWAleCJZUk5htrGg413t3GCy.jpg" year="" />
<twig:Poster imdbId="" title="Novocaine" description="" image="https://image.tmdb.org/t/p/w500/xmMHGz9dVRaMY6rRAlEX4W0Wdhm.jpg" year="" />
<twig:Poster imdbId="" title="Gunslingers" description="" image="https://image.tmdb.org/t/p/w500/O7REXWPANWXvX2jhQydHjAq2DV.jpg" year="" />
<div class="flex flex-row w-full gap-2">
<twig:Card title="{{ results.results|length }} results for {{ results.term }}" contentClass="flex flex-col gap-4 justify-between w-full">
{% for result in results.results %}
<ul>
<li>
<twig:SearchResult
mediaType="{{ result.mediaType }}"
title="{{ result.title }}"
year="{{ result.year }}"
description="{{ result.description }}"
poster="{{ result.poster }}"
imdbId="{{ result.imdbId }}"
/>
</li>
</ul>
{% endfor %}
</twig:Card>
</div>
</div>