Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
602d3cd
commit 920ad63
Showing
14 changed files
with
11,775 additions
and
174 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,201 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace App\Command; | ||
|
||
use Psr\Log\LoggerInterface; | ||
use Symfony\Component\Console\Attribute\AsCommand; | ||
use Symfony\Component\Console\Command\Command; | ||
use Symfony\Component\Console\Input\InputArgument; | ||
use Symfony\Component\Console\Input\InputInterface; | ||
use Symfony\Component\Console\Output\OutputInterface; | ||
use Symfony\Component\Console\Style\SymfonyStyle; | ||
use Symfony\Component\DependencyInjection\Attribute\Autowire; | ||
use Symfony\Component\HttpFoundation\Request; | ||
use Symfony\Component\Serializer\Encoder\DecoderInterface; | ||
use Symfony\Component\Serializer\Encoder\JsonDecode; | ||
use Symfony\Component\Serializer\Encoder\JsonEncode; | ||
use Symfony\Component\Serializer\SerializerInterface; | ||
use Symfony\Contracts\HttpClient\HttpClientInterface; | ||
|
||
#[AsCommand( | ||
name: 'app:books:mock', | ||
description: 'Import books from Open Library and store their data in a JSON for fixtures loading.', | ||
)] | ||
final class BooksMockCommand extends Command | ||
{ | ||
public function __construct( | ||
private readonly SerializerInterface $serializer, | ||
private readonly DecoderInterface $decoder, | ||
private readonly HttpClientInterface $client, | ||
private readonly LoggerInterface $logger, | ||
#[Autowire('%kernel.project_dir%')] | ||
private readonly string $projectDir, | ||
) { | ||
parent::__construct(); | ||
} | ||
|
||
protected function configure(): void | ||
{ | ||
$this | ||
->addArgument( | ||
name: 'source', | ||
mode: InputArgument::REQUIRED, | ||
description: 'Source file (created by app:books:import command)', | ||
) | ||
; | ||
} | ||
|
||
protected function execute(InputInterface $input, OutputInterface $output): int | ||
{ | ||
$io = new SymfonyStyle($input, $output); | ||
|
||
$source = realpath($this->projectDir . '/' . $input->getArgument('source')); | ||
|
||
$mocks = []; | ||
foreach (['Foundation Isaac Asimov', 'Eon Greg Bear', 'Hyperion Dan Simmons'] as $query) { | ||
$searchUri = 'https://openlibrary.org/search.json?q=' . $query . '&limit=10'; | ||
$this->logger->debug('Importing search query.', [ | ||
'query' => $searchUri, | ||
]); | ||
$docs = $this->getData($searchUri)['docs']; | ||
$docs = array_map(static fn ($doc): array => array_filter( | ||
$doc, | ||
static fn (string $key): bool => \in_array($key, ['title', 'author_name', 'seed'], true), | ||
\ARRAY_FILTER_USE_KEY | ||
), $docs); | ||
$mocks[] = $this->mockifyJson( | ||
$searchUri, | ||
$docs | ||
); | ||
} | ||
|
||
$this->logger->debug('Importing source file.', [ | ||
'source' => $source, | ||
]); | ||
$data = $this->decoder->decode(file_get_contents($source), 'json', [JsonDecode::DETAILED_ERROR_MESSAGES => true]); | ||
$io->progressStart(\count($data)); | ||
|
||
foreach ($data as $datum) { | ||
$bookUri = $datum['book']; | ||
$this->logger->info('Importing book.', ['book' => $bookUri]); | ||
$book = $this->getData($bookUri); | ||
|
||
$mocks[] = $this->mockifyJson( | ||
$bookUri, | ||
array_filter( | ||
$book, | ||
// only save required data (cf. pwa/utils/book.ts) | ||
static fn (string $key): bool => \in_array($key, [ | ||
'publish_date', | ||
'covers', | ||
'description', | ||
'works', | ||
], true), | ||
\ARRAY_FILTER_USE_KEY | ||
), | ||
); | ||
|
||
$coverId = null; | ||
if (isset($book['covers'][0])) { | ||
$coverId = $book['covers'][0]; | ||
} | ||
|
||
if (isset($book['works'][0]['key'])) { | ||
$workUri = 'https://openlibrary.org' . $book['works'][0]['key']; | ||
$this->logger->info('Importing work.', ['work' => $workUri]); | ||
$work = $this->getData($workUri); | ||
$mocks[] = $this->mockifyJson( | ||
$workUri, | ||
array_filter( | ||
$work, | ||
// only save required data (cf. pwa/utils/book.ts) | ||
static fn (string $key): bool => \in_array($key, [ | ||
'covers', | ||
'description', | ||
], true), | ||
\ARRAY_FILTER_USE_KEY | ||
), | ||
); | ||
|
||
if (!isset($book['covers'][0]) && isset($work['covers'][0])) { | ||
$coverId = $work['covers'][0]; | ||
} | ||
} | ||
|
||
if ($coverId) { | ||
// no need to save all images for e2e tests (except for default book) | ||
// all other books are only tested in BookList, using medium image only | ||
$coverUri = 'https://covers.openlibrary.org/b/id/' . $coverId . '-M.jpg'; | ||
$this->logger->info('Downloading cover.', ['cover' => $coverUri]); | ||
$response = $this->client->request('GET', $coverUri, [ | ||
'buffer' => false, | ||
]); | ||
if (200 !== $response->getStatusCode()) { | ||
throw new \RuntimeException('An error occurred while downloading image "' . $coverUri . '".'); | ||
} | ||
$cover = ''; | ||
foreach ($this->client->stream($response) as $chunk) { | ||
$cover .= $chunk->getContent(); | ||
} | ||
$mocks[] = $this->mockifyJpg($coverUri, base64_encode($cover)); | ||
} | ||
|
||
$io->progressAdvance(); | ||
} | ||
|
||
$io->progressFinish(); | ||
|
||
$output->write($this->serializer->serialize($mocks, 'json', [JsonEncode::OPTIONS => \JSON_PRETTY_PRINT])); | ||
|
||
return Command::SUCCESS; | ||
} | ||
|
||
private function getData(string $uri): array | ||
{ | ||
return $this->decoder->decode($this->client->request(Request::METHOD_GET, $uri, [ | ||
'headers' => [ | ||
'Accept' => 'application/json', | ||
], | ||
])->getContent(), 'json'); | ||
} | ||
|
||
private function mockifyJson(string $uri, array $body): array | ||
{ | ||
return [ | ||
'httpRequest' => [ | ||
'method' => 'GET', | ||
'path' => preg_replace('/^https:\/\/openlibrary\.org(.*)$/', '$1', $uri), | ||
], | ||
'httpResponse' => [ | ||
'headers' => [ | ||
'Content-Type' => ['application/json; charset=utf-8'], | ||
], | ||
'body' => $this->serializer->serialize($body, 'json'), | ||
], | ||
]; | ||
} | ||
|
||
private function mockifyJpg(string $uri, string $body): array | ||
{ | ||
$filename = pathinfo($uri, \PATHINFO_BASENAME); | ||
|
||
return [ | ||
'httpRequest' => [ | ||
'method' => 'GET', | ||
'path' => preg_replace('/^https:\/\/covers\.openlibrary\.org(.*)$/', '$1', $uri), | ||
], | ||
'httpResponse' => [ | ||
'headers' => [ | ||
'Content-Type' => ['image/jpg'], | ||
'Content-Disposition' => ['form-data; name="' . $filename . '"; filename="' . $filename . '"'], | ||
], | ||
'body' => [ | ||
'type' => 'BINARY', | ||
'base64Bytes' => $body, | ||
], | ||
], | ||
]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
Oops, something went wrong.