940afe9319
API CI/CD / Validate (composer + pint) (push) Successful in 2m7s
API CI/CD / Test (PHPUnit) (push) Failing after 2m23s
API CI/CD / Build frontend assets (push) Successful in 2m18s
API CI/CD / Security audit (push) Successful in 31s
API CI/CD / Deploy to shared hosting (PHP) (push) Has been skipped
160 lines
4.3 KiB
PHP
160 lines
4.3 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Docs;
|
|
|
|
use Illuminate\Routing\Route;
|
|
use Illuminate\Support\Facades\Route as RouteFacade;
|
|
use Illuminate\Support\Str;
|
|
|
|
class OpenApiRouteExporter
|
|
{
|
|
public function exportFromFile(string $path): array
|
|
{
|
|
$base = [];
|
|
if (is_file($path)) {
|
|
$json = json_decode((string) file_get_contents($path), true);
|
|
if (is_array($json)) {
|
|
$base = $json;
|
|
}
|
|
}
|
|
|
|
$base['paths'] = $base['paths'] ?? [];
|
|
|
|
foreach (RouteFacade::getRoutes() as $route) {
|
|
$this->mergeRoute($base['paths'], $route);
|
|
}
|
|
|
|
return $this->redactSensitiveAuthTerms($base);
|
|
}
|
|
|
|
private function mergeRoute(array &$paths, Route $route): void
|
|
{
|
|
if (! $this->isDocumentableRoute($route)) {
|
|
return;
|
|
}
|
|
|
|
$uriRaw = ltrim($route->uri(), '/');
|
|
if (str_starts_with($uriRaw, 'api/')) {
|
|
$uri = '/'.$uriRaw;
|
|
} elseif (str_starts_with($uriRaw, 'v1/')) {
|
|
$uri = '/api/'.$uriRaw;
|
|
} elseif ($uriRaw === 'v1') {
|
|
$uri = '/api/v1';
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
if (! str_starts_with($uri, '/api/v1')) {
|
|
return;
|
|
}
|
|
|
|
$methods = array_diff($route->methods(), ['HEAD']);
|
|
if (empty($methods)) {
|
|
return;
|
|
}
|
|
|
|
$tag = $this->resolveTag($uri);
|
|
foreach ($methods as $method) {
|
|
$lower = strtolower($method);
|
|
if (! isset($paths[$uri][$lower])) {
|
|
$paths[$uri][$lower] = [
|
|
'tags' => [$tag],
|
|
'summary' => $this->summaryFromAction($route),
|
|
'responses' => [
|
|
'200' => [
|
|
'description' => 'OK',
|
|
],
|
|
],
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
private function isDocumentableRoute(Route $route): bool
|
|
{
|
|
$uri = ltrim($route->uri(), '/');
|
|
if (! str_starts_with($uri, 'api/v1') && ! str_starts_with($uri, 'v1/')) {
|
|
return false;
|
|
}
|
|
|
|
$action = $route->getActionName();
|
|
if (! is_string($action)) {
|
|
return false;
|
|
}
|
|
|
|
if (str_contains($action, 'DocsController')) {
|
|
return false;
|
|
}
|
|
|
|
if ($action === 'Closure') {
|
|
return true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private function resolveTag(string $uri): string
|
|
{
|
|
$trimmed = trim(str_replace('/api/v1', '', $uri), '/');
|
|
if ($trimmed === '') {
|
|
return 'api';
|
|
}
|
|
$segments = explode('/', $trimmed);
|
|
$segment = $segments[0] ?: 'api';
|
|
$secondary = $segments[1] ?? null;
|
|
$normalized = Str::title(str_replace('-', ' ', $segment));
|
|
$overrides = [
|
|
'Whatsapp' => 'WhatsApp',
|
|
];
|
|
|
|
if ($segment === 'finance' && $secondary === 'refunds') {
|
|
return 'Refunds';
|
|
}
|
|
|
|
return $overrides[$normalized] ?? $normalized;
|
|
}
|
|
|
|
private function summaryFromAction(Route $route): string
|
|
{
|
|
$action = $route->getActionName();
|
|
if (is_string($action) && str_contains($action, '@')) {
|
|
[$controller, $method] = explode('@', $action, 2);
|
|
$base = class_basename($controller);
|
|
|
|
return $base.'::'.$method;
|
|
}
|
|
if (is_string($action)) {
|
|
return $action;
|
|
}
|
|
|
|
return 'Endpoint';
|
|
}
|
|
|
|
private function redactSensitiveAuthTerms(mixed $value): mixed
|
|
{
|
|
if (is_array($value)) {
|
|
$redacted = [];
|
|
foreach ($value as $key => $child) {
|
|
$redacted[$this->redactSensitiveAuthString((string) $key)] = $this->redactSensitiveAuthTerms($child);
|
|
}
|
|
|
|
return $redacted;
|
|
}
|
|
|
|
if (is_string($value)) {
|
|
return $this->redactSensitiveAuthString($value);
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
|
|
private function redactSensitiveAuthString(string $value): string
|
|
{
|
|
return str_ireplace(
|
|
['remember_token', 'two_factor', 'api_token', 'provider_token', 'password', 'secret'],
|
|
['auth_token_reference', 'mfa', 'api_credential', 'provider_credential', 'credential', 'sensitive_value'],
|
|
$value,
|
|
);
|
|
}
|
|
}
|