Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
53 / 53
100.00% covered (success)
100.00%
7 / 7
CRAP
100.00% covered (success)
100.00%
1 / 1
PdfApiController
100.00% covered (success)
100.00%
52 / 52
100.00% covered (success)
100.00%
7 / 7
13
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 listTemplates
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
 listVariables
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 compileBlocks
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 listVersions
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
3
 rollbackVersion
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
 generate
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4
5/** @license For full copyright and license information, please see the LICENSE.md file. */
6
7namespace App\Modules\Pdf\Presentation\Api;
8
9defined('AMMONLY_APP') || exit('Direct script access is forbidden.');
10
11use App\Core\Template\Application\Service\TemplateCompilerService;
12use App\Core\Template\Application\Service\TemplateVariableResolverInterface;
13use App\Modules\Pdf\Application\Service\PdfGeneratorServiceInterface;
14use App\Modules\Pdf\Domain\Repository\PdfTemplateRepositoryInterface;
15use App\Modules\Pdf\Domain\Repository\PdfTemplateVersionRepositoryInterface;
16use App\Shared\Infrastructure\Http\ApiResponseTrait;
17use Psr\Http\Message\ResponseFactoryInterface;
18use Psr\Http\Message\ResponseInterface;
19use Psr\Http\Message\ServerRequestInterface;
20use Throwable;
21
22/**
23 * REST API Controller for PDF Templates, Versions, and Document Generation.
24 *
25 * @package App\Modules\Pdf\Presentation\Api
26 */
27final readonly class PdfApiController
28{
29    use ApiResponseTrait;
30
31    /**
32     * PdfApiController constructor.
33     *
34     * @param ResponseFactoryInterface                $responseFactory Response factory.
35     * @param PdfTemplateRepositoryInterface          $repository      Template repository.
36     * @param PdfGeneratorServiceInterface             $generator       Generator service.
37     * @param TemplateVariableResolverInterface        $resolver        Variable resolver.
38     * @param TemplateCompilerService                 $compiler        Template compiler.
39     * @param PdfTemplateVersionRepositoryInterface|null $versionRepo   Version history repository.
40     */
41    public function __construct(
42        private ResponseFactoryInterface $responseFactory,
43        private PdfTemplateRepositoryInterface $repository,
44        private PdfGeneratorServiceInterface $generator,
45        private TemplateVariableResolverInterface $resolver,
46        private TemplateCompilerService $compiler,
47        private ?PdfTemplateVersionRepositoryInterface $versionRepo = null
48    ) {
49    }
50
51    /**
52     * Handles GET /api/v1/pdf/templates?module={moduleName}
53     *
54     * @param ServerRequestInterface $request HTTP request.
55     * @return ResponseInterface JSON list of active templates.
56     */
57    public function listTemplates(ServerRequestInterface $request): ResponseInterface
58    {
59        $queryParams = $request->getQueryParams();
60        $module = (string)($queryParams['module'] ?? 'global');
61        $lang = (string)($queryParams['lang'] ?? 'pl');
62
63        $templates = $this->repository->findAllActiveByModule($module, $lang);
64        $data = [];
65        foreach ($templates as $tpl) {
66            $data[] = $tpl->toArray();
67        }
68
69        return $this->jsonSuccess($this->responseFactory, $data);
70    }
71
72    /**
73     * Handles GET /api/v1/pdf/variables?module={moduleName}
74     *
75     * @param ServerRequestInterface $request HTTP request.
76     * @return ResponseInterface JSON categorized variable tree for Variable Picker.
77     */
78    public function listVariables(ServerRequestInterface $request): ResponseInterface
79    {
80        $queryParams = $request->getQueryParams();
81        $module = (string)($queryParams['module'] ?? 'tickets');
82
83        $tree = $this->resolver->getAvailableVariables($module);
84
85        return $this->jsonSuccess($this->responseFactory, $tree);
86    }
87
88    /**
89     * Handles POST /api/v1/pdf/compile-blocks
90     *
91     * @param ServerRequestInterface $request HTTP request.
92     * @return ResponseInterface JSON with compiled HTML.
93     */
94    public function compileBlocks(ServerRequestInterface $request): ResponseInterface
95    {
96        $payload = $this->parseJsonBody($request);
97        $compiledHtml = $this->compiler->compileBlocks($payload);
98
99        return $this->jsonSuccess($this->responseFactory, [
100            'compiled_html' => $compiledHtml,
101        ]);
102    }
103
104    /**
105     * Handles GET /api/v1/pdf/templates/{id}/versions
106     *
107     * @param int $templateId Template ID.
108     * @return ResponseInterface JSON list of historical revisions.
109     */
110    public function listVersions(int $templateId): ResponseInterface
111    {
112        if ($this->versionRepo === null) {
113            return $this->jsonSuccess($this->responseFactory, []);
114        }
115
116        $versions = $this->versionRepo->findVersionsByTemplateId($templateId);
117        $data = [];
118        foreach ($versions as $v) {
119            $data[] = [
120                'id'             => $v->id,
121                'version_number' => $v->versionNumber,
122                'change_summary' => $v->changeSummary,
123                'created_at'     => $v->createdAt,
124                'created_by'     => $v->createdBy,
125            ];
126        }
127
128        return $this->jsonSuccess($this->responseFactory, $data);
129    }
130
131    /**
132     * Handles POST /api/v1/pdf/templates/{id}/versions/{versionId}/rollback
133     *
134     * @param int $templateId Template ID.
135     * @param int $versionId  Version snapshot ID to restore.
136     * @return ResponseInterface JSON result with updated template data.
137     */
138    public function rollbackVersion(
139        int $templateId,
140        int $versionId
141    ): ResponseInterface {
142        if ($this->versionRepo === null) {
143            return $this->jsonError($this->responseFactory, 'Version repository unavailable.', 503);
144        }
145
146        try {
147            $updated = $this->versionRepo->rollbackToVersion($templateId, $versionId);
148            return $this->jsonSuccess($this->responseFactory, [
149                'status'   => 'restored',
150                'template' => $updated->toArray(),
151            ]);
152        } catch (Throwable $e) {
153            return $this->jsonError($this->responseFactory, $e->getMessage(), 400);
154        }
155    }
156
157    /**
158     * Handles GET /api/v1/pdf/generate/{templateId}/{recordId}
159     *
160     * @param ServerRequestInterface $request    HTTP request.
161     * @param int                    $templateId Template ID.
162     * @param int                    $recordId   Record ID.
163     * @return ResponseInterface JSON metadata with download URL.
164     */
165    public function generate(ServerRequestInterface $request, int $templateId, int $recordId): ResponseInterface
166    {
167        $queryParams = $request->getQueryParams();
168        $module = (string)($queryParams['module'] ?? 'tickets');
169
170        try {
171            $result = $this->generator->generateForRecord($templateId, $module, $recordId);
172
173            return $this->jsonSuccess($this->responseFactory, [
174                'filename'          => $result['filename'],
175                'download_url'      => '/pdf/download/' . $templateId . '/' . $recordId . '?module=' . $module,
176                'verification_hash' => $result['verification_hash'] ?? null,
177                'checksum_sha256'   => $result['checksum_sha256'] ?? null,
178                'status'            => 'generated',
179            ]);
180        } catch (Throwable $e) {
181            return $this->jsonError($this->responseFactory, $e->getMessage(), 400);
182        }
183    }
184}