fn_b/app/Services/Editorial/ProcessApi.php

67 lines
3.1 KiB
PHP

<?php
namespace App\Services\Editorial;
use Illuminate\Contracts\Http\Kernel;
use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
/** Calls the existing HTTP routes on the application server, without opening its public API. */
final class ProcessApi
{
private array $credential;
public function __construct()
{
abort_unless(PHP_SAPI === 'cli', 403);
$this->credential = ['schema' => 'gemdocs.credential.v1', 'project' => 'fna-v3',
'environment' => app()->environment(), 'base_url' => config('editorial.base_url'),
'expires_at' => time() + 600, 'fields' => ['api_key' => 'fna_ed_'.bin2hex(random_bytes(32))]];
}
public function call(string $method, string $path, array $data = [], ?UploadedFile $image = null): array
{
abort_unless(in_array($method, ['GET', 'POST']) && preg_match('~\A[a-zA-Z0-9_/-]+\z~', $path), 422);
$response = $this->dispatch($method, '/api/editorial/v1/'.$path, $data, $image);
$body = json_decode($response->getContent(), true);
abort_unless($response->isSuccessful(), $response->getStatusCode(), 'Editorial API rejected operation; inspect state before retry');
abort_unless(is_array($body), 502, 'Unexpected API response');
return $body;
}
public function preview(string $url): string
{
$base = rtrim(config('editorial.base_url'), '/');
abort_unless(str_starts_with($url, $base.'/api/editorial/v1/preview/'), 422, 'Preview origin mismatch');
$response = $this->dispatch('GET', substr($url, strlen($base)));
abort_unless($response->isSuccessful() && str_contains($response->headers->get('Content-Type', ''), 'text/html')
&& str_contains($response->headers->get('X-Robots-Tag', ''), 'noindex'), 502, 'Preview render failed');
return $response->getContent();
}
private function dispatch(string $method, string $path, array $data = [], ?UploadedFile $image = null)
{
abort_unless(time() < $this->credential['expires_at'], 401, 'Process session expired');
$before = ['editorial.enabled' => config('editorial.enabled'),
'editorial.process_credential' => config('editorial.process_credential')];
$originalRequest = app('request');
config(['editorial.enabled' => true, 'editorial.process_credential' => $this->credential]);
try {
$server = ['HTTP_ACCEPT' => 'application/json', 'HTTP_AUTHORIZATION' => 'Bearer '.$this->credential['fields']['api_key'],
'CONTENT_TYPE' => 'application/json', 'REMOTE_ADDR' => '127.0.0.1'];
$request = Request::create(rtrim(config('editorial.base_url'), '/').$path, $method, [], [],
$image ? ['image' => $image] : [], $server, $image ? null : json_encode($data));
$kernel = app(Kernel::class);
$response = $kernel->handle($request);
$kernel->terminate($request, $response);
return $response;
} finally {
config($before);
app()->instance('request', $originalRequest);
}
}
}