Added PDF generation service for management reports as per Spec 378, including Gotenberg integration in docker-compose and configuration updates. Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #449
115 lines
3.6 KiB
PHP
115 lines
3.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services\Pdf;
|
|
|
|
final class PdfRenderingGateway
|
|
{
|
|
public function __construct(
|
|
private readonly PdfRendererClient $client,
|
|
) {}
|
|
|
|
public function healthCheck(?string $correlationId = null): PdfRenderResult
|
|
{
|
|
if (! $this->isEnabled()) {
|
|
return PdfRenderResult::failure(
|
|
PdfRenderResult::RENDERER_DISABLED,
|
|
'PDF renderer is disabled.',
|
|
correlationId: $correlationId,
|
|
);
|
|
}
|
|
|
|
return $this->client->healthCheck($correlationId);
|
|
}
|
|
|
|
public function renderHtml(PdfRenderRequest $request): PdfRenderResult
|
|
{
|
|
if (! $this->isEnabled()) {
|
|
return PdfRenderResult::failure(
|
|
PdfRenderResult::RENDERER_DISABLED,
|
|
'PDF renderer is disabled.',
|
|
correlationId: $request->correlationId,
|
|
);
|
|
}
|
|
|
|
if (trim($request->html) === '') {
|
|
return PdfRenderResult::failure(
|
|
PdfRenderResult::INVALID_REQUEST,
|
|
'PDF renderer requires server-generated HTML.',
|
|
correlationId: $request->correlationId,
|
|
);
|
|
}
|
|
|
|
if (strlen($request->html) > $this->maxHtmlBytes()) {
|
|
return PdfRenderResult::failure(
|
|
PdfRenderResult::PAYLOAD_TOO_LARGE,
|
|
'PDF HTML payload exceeds the configured size limit.',
|
|
correlationId: $request->correlationId,
|
|
);
|
|
}
|
|
|
|
foreach ($request->assets as $filename => $contents) {
|
|
if (! $this->isFlatAssetFilename((string) $filename)) {
|
|
return PdfRenderResult::failure(
|
|
PdfRenderResult::INVALID_ASSET,
|
|
'PDF assets must use flat relative filenames.',
|
|
correlationId: $request->correlationId,
|
|
);
|
|
}
|
|
|
|
if (strlen($contents) > $this->maxAssetBytes()) {
|
|
return PdfRenderResult::failure(
|
|
PdfRenderResult::PAYLOAD_TOO_LARGE,
|
|
'PDF asset payload exceeds the configured size limit.',
|
|
correlationId: $request->correlationId,
|
|
);
|
|
}
|
|
}
|
|
|
|
$result = $this->client->render($request);
|
|
|
|
if ($result->successful() && $result->pdfBytes !== null && strlen($result->pdfBytes) > $this->maxOutputBytes()) {
|
|
return PdfRenderResult::failure(
|
|
PdfRenderResult::OUTPUT_TOO_LARGE,
|
|
'PDF renderer output exceeds the configured size limit.',
|
|
statusCode: $result->statusCode,
|
|
correlationId: $result->correlationId,
|
|
);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
private function isEnabled(): bool
|
|
{
|
|
return (bool) config('tenantpilot.pdf_renderer.enabled', false)
|
|
&& config('tenantpilot.pdf_renderer.driver', 'gotenberg') === 'gotenberg';
|
|
}
|
|
|
|
private function maxHtmlBytes(): int
|
|
{
|
|
return max(1, (int) config('tenantpilot.pdf_renderer.max_html_bytes', 1024 * 1024));
|
|
}
|
|
|
|
private function maxAssetBytes(): int
|
|
{
|
|
return max(1, (int) config('tenantpilot.pdf_renderer.max_asset_bytes', 2 * 1024 * 1024));
|
|
}
|
|
|
|
private function maxOutputBytes(): int
|
|
{
|
|
return max(1, (int) config('tenantpilot.pdf_renderer.max_output_bytes', 10 * 1024 * 1024));
|
|
}
|
|
|
|
private function isFlatAssetFilename(string $filename): bool
|
|
{
|
|
$filename = trim($filename);
|
|
|
|
return $filename !== ''
|
|
&& $filename === basename($filename)
|
|
&& ! str_contains($filename, "\0")
|
|
&& $filename !== 'index.html';
|
|
}
|
|
}
|