Skip to content

Commit 6518d03

Browse files
Merge pull request #949 from matomo-org/PG-5043-fetch-api-specs
Added simple script to fetch API specs, #PG-5043
2 parents f03e39d + 6ef6801 commit 6518d03

1 file changed

Lines changed: 168 additions & 0 deletions

File tree

fetch-openapi-specs.php

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
$baseUrl = rtrim(getenv('MATOMO_BASE_URL') ?: 'https://demo.matomo.cloud/', '/');
6+
$tokenAuth = getenv('MATOMO_TOKEN_AUTH') ?: 'anonymous';
7+
$targetDirectory = __DIR__ . '/app/public/openapi';
8+
$version = '1.0.0';
9+
10+
if (!is_dir($targetDirectory)) {
11+
fwrite(STDERR, "Target directory does not exist: $targetDirectory\n");
12+
exit(1);
13+
}
14+
15+
if (!function_exists('curl_init')) {
16+
fwrite(STDERR, "The curl extension is required to fetch OpenAPI specs.\n");
17+
exit(1);
18+
}
19+
20+
function buildRequestUrl(string $baseUrl, array $params): string
21+
{
22+
return $baseUrl . '/index.php?' . http_build_query($params);
23+
}
24+
25+
function describeRequest(string $baseUrl, array $params): string
26+
{
27+
$descriptionParams = $params;
28+
unset($descriptionParams['token_auth']);
29+
30+
return buildRequestUrl($baseUrl, $descriptionParams);
31+
}
32+
33+
/**
34+
* @return list<string>
35+
*/
36+
function findExistingSpecFiles(string $targetDirectory): array
37+
{
38+
$files = glob($targetDirectory . '/*_openapi_spec_v*.json');
39+
if ($files === false) {
40+
throw new RuntimeException("Failed to list existing OpenAPI spec files in $targetDirectory");
41+
}
42+
43+
return array_values($files);
44+
}
45+
46+
function removeStaleSpecFiles(string $targetDirectory, array $expectedPaths): void
47+
{
48+
$expectedPaths = array_fill_keys($expectedPaths, true);
49+
50+
foreach (findExistingSpecFiles($targetDirectory) as $existingPath) {
51+
if (isset($expectedPaths[$existingPath])) {
52+
continue;
53+
}
54+
55+
if (!unlink($existingPath)) {
56+
throw new RuntimeException("Failed to remove stale file: $existingPath");
57+
}
58+
}
59+
}
60+
61+
function fetchJson(string $baseUrl, string $tokenAuth, array $params): array
62+
{
63+
$params['module'] = 'API';
64+
$params['format'] = 'JSON';
65+
$params['token_auth'] = $tokenAuth;
66+
67+
$url = buildRequestUrl($baseUrl, $params);
68+
$requestDescription = describeRequest($baseUrl, $params);
69+
$ch = curl_init($url);
70+
if ($ch === false) {
71+
throw new RuntimeException("Failed to initialize curl for $requestDescription");
72+
}
73+
74+
curl_setopt_array($ch, [
75+
CURLOPT_RETURNTRANSFER => true,
76+
CURLOPT_FOLLOWLOCATION => true,
77+
CURLOPT_CONNECTTIMEOUT => 10,
78+
CURLOPT_TIMEOUT => 60,
79+
]);
80+
81+
$response = curl_exec($ch);
82+
if ($response === false) {
83+
$error = curl_error($ch);
84+
curl_close($ch);
85+
throw new RuntimeException("Request failed for $requestDescription: $error");
86+
}
87+
88+
$statusCode = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
89+
curl_close($ch);
90+
91+
if ($statusCode < 200 || $statusCode >= 300) {
92+
throw new RuntimeException("Request failed for $requestDescription with HTTP status $statusCode");
93+
}
94+
95+
$decoded = json_decode($response, true);
96+
if (!is_array($decoded)) {
97+
throw new RuntimeException("Invalid JSON response for $requestDescription");
98+
}
99+
100+
if (isset($decoded['result']) && $decoded['result'] === 'error') {
101+
$message = is_string($decoded['message'] ?? null) ? $decoded['message'] : 'Unknown API error';
102+
throw new RuntimeException($message);
103+
}
104+
105+
return $decoded;
106+
}
107+
108+
try {
109+
$plugins = fetchJson($baseUrl, $tokenAuth, [
110+
'method' => 'OpenApiDocs.getPluginWhitelist',
111+
]);
112+
} catch (Throwable $e) {
113+
fwrite(STDERR, "Failed to fetch plugin whitelist: {$e->getMessage()}\n");
114+
exit(1);
115+
}
116+
117+
$written = 0;
118+
$expectedPaths = [];
119+
120+
foreach ($plugins as $plugin) {
121+
if (!is_string($plugin) || $plugin === '') {
122+
continue;
123+
}
124+
125+
try {
126+
$spec = fetchJson($baseUrl, $tokenAuth, [
127+
'method' => 'OpenApiDocs.getGeneratedOpenApiSpec',
128+
'plugin' => $plugin,
129+
]);
130+
131+
$path = sprintf('%s/%s_openapi_spec_v%s.json', $targetDirectory, $plugin, $version);
132+
$expectedPaths[] = $path;
133+
$json = json_encode($spec, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
134+
if ($json === false) {
135+
throw new RuntimeException("Failed to encode JSON for plugin $plugin");
136+
}
137+
138+
$json .= "\n";
139+
$tempPath = tempnam($targetDirectory, $plugin . '_openapi_');
140+
if ($tempPath === false) {
141+
throw new RuntimeException("Failed to create temporary file for plugin $plugin");
142+
}
143+
144+
if (file_put_contents($tempPath, $json) === false) {
145+
@unlink($tempPath);
146+
throw new RuntimeException("Failed to write file: $path");
147+
}
148+
149+
if (!rename($tempPath, $path)) {
150+
@unlink($tempPath);
151+
throw new RuntimeException("Failed to move temporary file into place: $path");
152+
}
153+
154+
$written++;
155+
} catch (Throwable $e) {
156+
fwrite(STDERR, "Failed for $plugin: {$e->getMessage()}\n");
157+
exit(1);
158+
}
159+
}
160+
161+
try {
162+
removeStaleSpecFiles($targetDirectory, $expectedPaths);
163+
} catch (Throwable $e) {
164+
fwrite(STDERR, "Failed to clean up stale spec files: {$e->getMessage()}\n");
165+
exit(1);
166+
}
167+
168+
echo "Wrote $written spec file(s) to $targetDirectory\n";

0 commit comments

Comments
 (0)