@maxisoft/instantcms-mcp 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +308 -0
- package/dist/__tests__/artifact-tool.test.js +196 -0
- package/dist/__tests__/db-tool.test.js +152 -0
- package/dist/__tests__/define-tool.test.js +106 -0
- package/dist/__tests__/find-tool.test.js +185 -0
- package/dist/__tests__/get-component-api.test.js +144 -0
- package/dist/__tests__/hardening.test.js +73 -0
- package/dist/__tests__/hooks-tool.test.js +173 -0
- package/dist/__tests__/mcp-integration-extra.test.js +166 -0
- package/dist/__tests__/mcp-integration.test.js +53 -0
- package/dist/__tests__/pagination.test.js +114 -0
- package/dist/__tests__/performance-baseline.test.js +56 -0
- package/dist/__tests__/project-patch.test.js +144 -0
- package/dist/__tests__/project-source.test.js +44 -0
- package/dist/__tests__/project-workflows-extra.test.js +165 -0
- package/dist/__tests__/project-workflows.test.js +51 -0
- package/dist/__tests__/scaffold-addon-roundtrip.test.js +287 -0
- package/dist/__tests__/serialization.test.js +170 -0
- package/dist/__tests__/source-knowledge-parsers.test.js +42 -0
- package/dist/__tests__/template-development.test.js +78 -0
- package/dist/__tests__/template-productivity.test.js +87 -0
- package/dist/__tests__/tools.test.js +2143 -0
- package/dist/data/components.js +2818 -0
- package/dist/data/controllers-map.js +7571 -0
- package/dist/data/core-api.js +9356 -0
- package/dist/data/database-schema.js +4555 -0
- package/dist/data/events-map.js +1339 -0
- package/dist/data/fields-map.js +3692 -0
- package/dist/data/hooks.js +2257 -0
- package/dist/data/js-api.js +123 -0
- package/dist/data/libs-api.js +264 -0
- package/dist/data/routes-map.js +203 -0
- package/dist/data/schemas-validation.js +255 -0
- package/dist/data/schemas.js +1404 -0
- package/dist/data/traits-map.js +1004 -0
- package/dist/data/version-profiles.js +41 -0
- package/dist/data/widgets-map.js +131 -0
- package/dist/data/wysiwyg-map.js +486 -0
- package/dist/generated/components-source.js +14748 -0
- package/dist/generated/hooks-source.js +3399 -0
- package/dist/generated/knowledge-meta.js +101 -0
- package/dist/index.js +13 -0
- package/dist/registry/database-tools.js +112 -0
- package/dist/registry/extension-tools.js +473 -0
- package/dist/registry/generator-tools.js +506 -0
- package/dist/registry/knowledge-tools.js +441 -0
- package/dist/registry/language-tools.js +99 -0
- package/dist/registry/meta-tools.js +152 -0
- package/dist/registry/project-tools.js +48 -0
- package/dist/registry/resources.js +98 -0
- package/dist/registry/source-tools.js +211 -0
- package/dist/registry/template-development-tools.js +79 -0
- package/dist/server.js +32 -0
- package/dist/tools/addon-tool.js +1437 -0
- package/dist/tools/admin-partial-tool.js +498 -0
- package/dist/tools/api-tool.js +294 -0
- package/dist/tools/artifact-tool.js +95 -0
- package/dist/tools/cache-tool.js +360 -0
- package/dist/tools/component-tool.js +415 -0
- package/dist/tools/controllers-tool.js +125 -0
- package/dist/tools/cron-tool.js +197 -0
- package/dist/tools/crud-tool.js +659 -0
- package/dist/tools/db-tool.js +198 -0
- package/dist/tools/email-tool.js +222 -0
- package/dist/tools/external-api-tool.js +596 -0
- package/dist/tools/filter-tool.js +341 -0
- package/dist/tools/form-tool.js +275 -0
- package/dist/tools/grid-tool.js +189 -0
- package/dist/tools/hooks-tool.js +104 -0
- package/dist/tools/import-export-tool.js +548 -0
- package/dist/tools/lang-tool.js +251 -0
- package/dist/tools/layout-override-tool.js +125 -0
- package/dist/tools/layout-tool.js +548 -0
- package/dist/tools/maria-tool.js +127 -0
- package/dist/tools/mariadb.js +201 -0
- package/dist/tools/migration-tool.js +329 -0
- package/dist/tools/oauth-tool.js +520 -0
- package/dist/tools/parser/components-parser.js +76 -0
- package/dist/tools/parser/controllers-parser.js +313 -0
- package/dist/tools/parser/core-parser.js +294 -0
- package/dist/tools/parser/coverage-generator.js +424 -0
- package/dist/tools/parser/events-parser.js +127 -0
- package/dist/tools/parser/fields-parser.js +382 -0
- package/dist/tools/parser/hooks-parser.js +106 -0
- package/dist/tools/parser/sql-parser.js +197 -0
- package/dist/tools/parser/traits-parser.js +161 -0
- package/dist/tools/parser/widgets-parser.js +150 -0
- package/dist/tools/permission-tool.js +348 -0
- package/dist/tools/project-patch-tool.js +73 -0
- package/dist/tools/project-source-tool.js +188 -0
- package/dist/tools/project-workflow-tool.js +186 -0
- package/dist/tools/requirement-tool.js +212 -0
- package/dist/tools/scaffold-tool.js +815 -0
- package/dist/tools/seo-tool.js +412 -0
- package/dist/tools/source-tool.js +155 -0
- package/dist/tools/template-development-tool.js +271 -0
- package/dist/tools/template-overrides-tool.js +294 -0
- package/dist/tools/template-productivity-tool.js +319 -0
- package/dist/tools/template-tool.js +665 -0
- package/dist/tools/test-tool.js +183 -0
- package/dist/tools/webhook-tool.js +427 -0
- package/dist/tools/widget-tool.js +331 -0
- package/dist/tools/wysiwyg-tool.js +137 -0
- package/dist/types/scaffold.js +68 -0
- package/dist/utils/define-tool.js +37 -0
- package/dist/utils/find-tool.js +97 -0
- package/dist/utils/mcp-result.js +18 -0
- package/dist/utils/pagination.js +27 -0
- package/dist/utils/serialization.js +27 -0
- package/package.json +94 -0
|
@@ -0,0 +1,596 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview External API client scaffolding tool for InstantCMS
|
|
4
|
+
* Generates API clients with authentication, rate limiting, and caching support
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.externalApiToolSchema = exports.AuthTypeEnum = exports.HttpMethodEnum = void 0;
|
|
8
|
+
exports.scaffoldExternalApi = scaffoldExternalApi;
|
|
9
|
+
const zod_1 = require("zod");
|
|
10
|
+
const scaffold_1 = require("../types/scaffold");
|
|
11
|
+
/**
|
|
12
|
+
* HTTP methods for API endpoints
|
|
13
|
+
*/
|
|
14
|
+
exports.HttpMethodEnum = zod_1.z.enum(['GET', 'POST', 'PUT', 'DELETE', 'PATCH']);
|
|
15
|
+
/**
|
|
16
|
+
* Authentication types
|
|
17
|
+
*/
|
|
18
|
+
exports.AuthTypeEnum = zod_1.z.enum(['api_key', 'bearer', 'basic', 'oauth2']);
|
|
19
|
+
/**
|
|
20
|
+
* Generates API client class
|
|
21
|
+
*/
|
|
22
|
+
function generateApiClient(name, Name, baseUrl, endpoints, options) {
|
|
23
|
+
const endpointMethods = endpoints
|
|
24
|
+
.map(e => {
|
|
25
|
+
const methodName = e.method.toLowerCase();
|
|
26
|
+
const pathPart = e.path.replace(/^\//, '').replace(/\//g, '_').replace(/[{}]/g, '');
|
|
27
|
+
const funcName = `${methodName}_${pathPart}`;
|
|
28
|
+
return ` public function ${funcName}($params = []) {
|
|
29
|
+
return $this->request('${e.method}', '${e.path}', $params);
|
|
30
|
+
}`;
|
|
31
|
+
})
|
|
32
|
+
.join('\n\n');
|
|
33
|
+
return `<?php
|
|
34
|
+
// InstantCMS 2. ${name}/api/client.php
|
|
35
|
+
|
|
36
|
+
class ${Name}ApiClient {
|
|
37
|
+
private $base_url = '${baseUrl}';
|
|
38
|
+
private $auth = null;
|
|
39
|
+
private $timeout = ${options.timeout};
|
|
40
|
+
private $rate_limiter = null;
|
|
41
|
+
private $cache = null;
|
|
42
|
+
|
|
43
|
+
public function __construct() {
|
|
44
|
+
$this->auth = new ${Name}ApiAuth($this);
|
|
45
|
+
|
|
46
|
+
if (${options.use_rate_limit}) {
|
|
47
|
+
$this->rate_limiter = new ${Name}ApiRateLimiter();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (${options.use_cache}) {
|
|
51
|
+
$this->cache = new ${Name}ApiCache();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public function setBaseUrl($url) {
|
|
56
|
+
$this->base_url = $url;
|
|
57
|
+
return $this;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
public function setTimeout($timeout) {
|
|
61
|
+
$this->timeout = $timeout;
|
|
62
|
+
return $this;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public function request($method, $path, $params = [], $data = null) {
|
|
66
|
+
$method = strtoupper($method);
|
|
67
|
+
|
|
68
|
+
if ($this->rate_limiter && !$this->rate_limiter->canMakeRequest()) {
|
|
69
|
+
throw new ${Name}ApiRateLimitException('Rate limit exceeded');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
$url = rtrim($this->base_url, '/') . '/' . ltrim($path, '/');
|
|
73
|
+
|
|
74
|
+
$request = new ${Name}ApiRequest();
|
|
75
|
+
$request->setMethod($method);
|
|
76
|
+
$request->setUrl($url);
|
|
77
|
+
$request->setParams($params);
|
|
78
|
+
$request->setTimeout($this->timeout);
|
|
79
|
+
$request->setHeaders($this->auth->getHeaders());
|
|
80
|
+
|
|
81
|
+
if ($data !== null) {
|
|
82
|
+
$request->setBody($data);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
$result = $request->execute();
|
|
86
|
+
|
|
87
|
+
if ($this->rate_limiter) {
|
|
88
|
+
$this->rate_limiter->recordRequest();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return $result;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
${endpointMethods}
|
|
95
|
+
|
|
96
|
+
public function get($path, $params = []) {
|
|
97
|
+
return $this->request('GET', $path, $params);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
public function post($path, $data, $params = []) {
|
|
101
|
+
return $this->request('POST', $path, $params, $data);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
public function put($path, $data, $params = []) {
|
|
105
|
+
return $this->request('PUT', $path, $params, $data);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
public function delete($path, $params = []) {
|
|
109
|
+
return $this->request('DELETE', $path, $params);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
public function patch($path, $data, $params = []) {
|
|
113
|
+
return $this->request('PATCH', $path, $params, $data);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
public static function getInstance() {
|
|
117
|
+
static $instance = null;
|
|
118
|
+
if ($instance === null) {
|
|
119
|
+
$instance = new self();
|
|
120
|
+
}
|
|
121
|
+
return $instance;
|
|
122
|
+
}
|
|
123
|
+
}`;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Generates API request class
|
|
127
|
+
*/
|
|
128
|
+
function generateApiRequest(name, Name, _options) {
|
|
129
|
+
return `<?php
|
|
130
|
+
// InstantCMS 2. ${name}/api/request.php
|
|
131
|
+
|
|
132
|
+
class ${Name}ApiRequest {
|
|
133
|
+
private $method = 'GET';
|
|
134
|
+
private $url = '';
|
|
135
|
+
private $params = [];
|
|
136
|
+
private $headers = [];
|
|
137
|
+
private $body = null;
|
|
138
|
+
private $timeout = 30;
|
|
139
|
+
|
|
140
|
+
public function setMethod($method) {
|
|
141
|
+
$this->method = strtoupper($method);
|
|
142
|
+
return $this;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
public function setUrl($url) {
|
|
146
|
+
$this->url = $url;
|
|
147
|
+
return $this;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
public function setParams($params) {
|
|
151
|
+
$this->params = $params;
|
|
152
|
+
return $this;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
public function setHeaders($headers) {
|
|
156
|
+
$this->headers = array_merge($this->headers, $headers);
|
|
157
|
+
return $this;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
public function setBody($body) {
|
|
161
|
+
$this->body = $body;
|
|
162
|
+
return $this;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
public function setTimeout($timeout) {
|
|
166
|
+
$this->timeout = $timeout;
|
|
167
|
+
return $this;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
public function execute() {
|
|
171
|
+
$url = $this->url;
|
|
172
|
+
|
|
173
|
+
if (!empty($this->params) && in_array($this->method, ['GET', 'DELETE'])) {
|
|
174
|
+
$url .= '?' . http_build_query($this->params);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
$ch = curl_init();
|
|
178
|
+
|
|
179
|
+
curl_setopt($ch, CURLOPT_URL, $url);
|
|
180
|
+
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
181
|
+
curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);
|
|
182
|
+
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $this->method);
|
|
183
|
+
curl_setopt($ch, CURLOPT_HTTPHEADER, $this->buildHeaders());
|
|
184
|
+
|
|
185
|
+
if ($this->body !== null) {
|
|
186
|
+
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($this->body));
|
|
187
|
+
$this->headers['Content-Type'] = 'application/json';
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
$response = curl_exec($ch);
|
|
191
|
+
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
192
|
+
$error = curl_error($ch);
|
|
193
|
+
|
|
194
|
+
curl_close($ch);
|
|
195
|
+
|
|
196
|
+
if ($error) {
|
|
197
|
+
throw new ${Name}ApiException('cURL Error: ' . $error);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return $this->parseResponse($response, $http_code);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private function buildHeaders() {
|
|
204
|
+
$headers = [];
|
|
205
|
+
foreach ($this->headers as $key => $value) {
|
|
206
|
+
$headers[] = "$key: $value";
|
|
207
|
+
}
|
|
208
|
+
return $headers;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private function parseResponse($response, $http_code) {
|
|
212
|
+
$data = json_decode($response, true);
|
|
213
|
+
|
|
214
|
+
if ($http_code >= 400) {
|
|
215
|
+
$error_msg = $data['message'] ?? $data['error'] ?? 'Unknown error';
|
|
216
|
+
throw new ${Name}ApiHttpException($error_msg, $http_code);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return [
|
|
220
|
+
'success' => $http_code >= 200 && $http_code < 300,
|
|
221
|
+
'status_code' => $http_code,
|
|
222
|
+
'data' => $data,
|
|
223
|
+
];
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
class ${Name}ApiException extends Exception {}
|
|
228
|
+
class ${Name}ApiHttpException extends Exception {
|
|
229
|
+
private $status_code;
|
|
230
|
+
|
|
231
|
+
public function __construct($message, $status_code) {
|
|
232
|
+
parent::__construct($message);
|
|
233
|
+
$this->status_code = $status_code;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
public function getStatusCode() {
|
|
237
|
+
return $this->status_code;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
class ${Name}ApiRateLimitException extends Exception {}`;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Generates API authentication class
|
|
245
|
+
*/
|
|
246
|
+
function generateApiAuth(name, Name, options) {
|
|
247
|
+
let authImplementation = '';
|
|
248
|
+
switch (options.auth_type) {
|
|
249
|
+
case 'api_key':
|
|
250
|
+
authImplementation = ` public function getHeaders() {
|
|
251
|
+
return [
|
|
252
|
+
'X-API-Key' => $this->api_key,
|
|
253
|
+
];
|
|
254
|
+
}`;
|
|
255
|
+
break;
|
|
256
|
+
case 'basic':
|
|
257
|
+
authImplementation = ` public function getHeaders() {
|
|
258
|
+
return [
|
|
259
|
+
'Authorization' => 'Basic ' . base64_encode($this->api_key . ':' . $this->api_secret),
|
|
260
|
+
];
|
|
261
|
+
}`;
|
|
262
|
+
break;
|
|
263
|
+
case 'oauth2':
|
|
264
|
+
authImplementation = ` public function getHeaders() {
|
|
265
|
+
if ($this->access_token && $this->isTokenValid()) {
|
|
266
|
+
return [
|
|
267
|
+
'Authorization' => 'Bearer ' . $this->access_token,
|
|
268
|
+
];
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if ($this->refresh_token) {
|
|
272
|
+
$this->refreshAccessToken();
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return [];
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
private function isTokenValid() {
|
|
279
|
+
if (!$this->access_token || !$this->token_expires_at) {
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
return time() < $this->token_expires_at;
|
|
283
|
+
}`;
|
|
284
|
+
break;
|
|
285
|
+
default:
|
|
286
|
+
authImplementation = ` public function getHeaders() {
|
|
287
|
+
return [
|
|
288
|
+
'Authorization' => 'Bearer ' . $this->access_token,
|
|
289
|
+
];
|
|
290
|
+
}`;
|
|
291
|
+
}
|
|
292
|
+
return `<?php
|
|
293
|
+
// InstantCMS 2. ${name}/api/auth.php
|
|
294
|
+
|
|
295
|
+
class ${Name}ApiAuth {
|
|
296
|
+
private $client = null;
|
|
297
|
+
private $access_token = '';
|
|
298
|
+
private $refresh_token = '';
|
|
299
|
+
private $api_key = '';
|
|
300
|
+
private $api_secret = '';
|
|
301
|
+
private $token_expires_at = 0;
|
|
302
|
+
private $config = [];
|
|
303
|
+
|
|
304
|
+
public function __construct($client) {
|
|
305
|
+
$this->client = $client;
|
|
306
|
+
$this->loadConfig();
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
private function loadConfig() {
|
|
310
|
+
$config_path = cmsConfig::get('root_path') . '/system/config/api/${name}.php';
|
|
311
|
+
if (file_exists($config_path)) {
|
|
312
|
+
$this->config = include $config_path;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
$this->access_token = $this->config['access_token'] ?? '';
|
|
316
|
+
$this->refresh_token = $this->config['refresh_token'] ?? '';
|
|
317
|
+
$this->api_key = $this->config['api_key'] ?? '';
|
|
318
|
+
$this->api_secret = $this->config['api_secret'] ?? '';
|
|
319
|
+
$this->token_expires_at = $this->config['token_expires_at'] ?? 0;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
public function saveConfig() {
|
|
323
|
+
$config_path = cmsConfig::get('root_path') . '/system/config/api/${name}.php';
|
|
324
|
+
$config = [
|
|
325
|
+
'access_token' => $this->access_token,
|
|
326
|
+
'refresh_token' => $this->refresh_token,
|
|
327
|
+
'api_key' => $this->api_key,
|
|
328
|
+
'api_secret' => $this->api_secret,
|
|
329
|
+
'token_expires_at' => $this->token_expires_at,
|
|
330
|
+
];
|
|
331
|
+
file_put_contents($config_path, '<?php return ' . var_export($config, true) . ';');
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
${authImplementation}
|
|
335
|
+
|
|
336
|
+
public function setAccessToken($token, $expires_in = null) {
|
|
337
|
+
$this->access_token = $token;
|
|
338
|
+
if ($expires_in) {
|
|
339
|
+
$this->token_expires_at = time() + $expires_in;
|
|
340
|
+
}
|
|
341
|
+
$this->saveConfig();
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
public function setApiKey($key) {
|
|
345
|
+
$this->api_key = $key;
|
|
346
|
+
$this->saveConfig();
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
public function setApiSecret($secret) {
|
|
350
|
+
$this->api_secret = $secret;
|
|
351
|
+
$this->saveConfig();
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
public function isAuthenticated() {
|
|
355
|
+
return !empty($this->access_token);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
public function logout() {
|
|
359
|
+
$this->access_token = '';
|
|
360
|
+
$this->refresh_token = '';
|
|
361
|
+
$this->token_expires_at = 0;
|
|
362
|
+
$this->saveConfig();
|
|
363
|
+
}
|
|
364
|
+
}`;
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Generates rate limiter class
|
|
368
|
+
*/
|
|
369
|
+
function generateRateLimiter(name, Name, options) {
|
|
370
|
+
return `<?php
|
|
371
|
+
// InstantCMS 2. ${name}/api/rate_limiter.php
|
|
372
|
+
|
|
373
|
+
class ${Name}ApiRateLimiter {
|
|
374
|
+
private $requests = [];
|
|
375
|
+
private $limit = ${options.rate_limit};
|
|
376
|
+
private $window = 60;
|
|
377
|
+
|
|
378
|
+
public function __construct() {
|
|
379
|
+
$this->loadState();
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
private function loadState() {
|
|
383
|
+
$cache_key = '${name}_api_rate_limit';
|
|
384
|
+
$cached = cmsCache::getInstance()->get($cache_key);
|
|
385
|
+
if ($cached !== false) {
|
|
386
|
+
$this->requests = $cached;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
private function saveState() {
|
|
391
|
+
$cache_key = '${name}_api_rate_limit';
|
|
392
|
+
cmsCache::getInstance()->set($cache_key, $this->requests, 120);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
public function canMakeRequest() {
|
|
396
|
+
$this->cleanupOldRequests();
|
|
397
|
+
return count($this->requests) < $this->limit;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
public function recordRequest() {
|
|
401
|
+
$this->requests[] = time();
|
|
402
|
+
$this->saveState();
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
public function getRemainingRequests() {
|
|
406
|
+
$this->cleanupOldRequests();
|
|
407
|
+
return max(0, $this->limit - count($this->requests));
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
public function getResetTime() {
|
|
411
|
+
if (empty($this->requests)) {
|
|
412
|
+
return 0;
|
|
413
|
+
}
|
|
414
|
+
$oldest = min($this->requests);
|
|
415
|
+
return $oldest + $this->window - time();
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
private function cleanupOldRequests() {
|
|
419
|
+
$cutoff = time() - $this->window;
|
|
420
|
+
$this->requests = array_filter($this->requests, function ($timestamp) use ($cutoff) {
|
|
421
|
+
return $timestamp > $cutoff;
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
public function reset() {
|
|
426
|
+
$this->requests = [];
|
|
427
|
+
$this->saveState();
|
|
428
|
+
}
|
|
429
|
+
}`;
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Generates API cache class
|
|
433
|
+
*/
|
|
434
|
+
function generateApiCache(name, Name, _options) {
|
|
435
|
+
return `<?php
|
|
436
|
+
// InstantCMS 2. ${name}/api/cache.php
|
|
437
|
+
|
|
438
|
+
class ${Name}ApiCache {
|
|
439
|
+
private $ttl = 3600;
|
|
440
|
+
|
|
441
|
+
public function __construct($ttl = null) {
|
|
442
|
+
if ($ttl !== null) {
|
|
443
|
+
$this->ttl = $ttl;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
public function get($key) {
|
|
448
|
+
$cache_key = $this->buildKey($key);
|
|
449
|
+
return cmsCache::getInstance()->get($cache_key);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
public function set($key, $value, $ttl = null) {
|
|
453
|
+
$cache_key = $this->buildKey($key);
|
|
454
|
+
$ttl = $ttl ?? $this->ttl;
|
|
455
|
+
return cmsCache::getInstance()->set($cache_key, $value, $ttl);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
public function delete($key) {
|
|
459
|
+
$cache_key = $this->buildKey($key);
|
|
460
|
+
return cmsCache::getInstance()->delete($cache_key);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
public function flush() {
|
|
464
|
+
return cmsCache::getInstance()->clean();
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
private function buildKey($key) {
|
|
468
|
+
if (is_array($key)) {
|
|
469
|
+
$key = md5(json_encode($key));
|
|
470
|
+
}
|
|
471
|
+
return '${name}_api_' . $key;
|
|
472
|
+
}
|
|
473
|
+
}`;
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Generates API hook handlers
|
|
477
|
+
*/
|
|
478
|
+
function generateApiHooks(name, Name, _endpoints, _options) {
|
|
479
|
+
return `<?php
|
|
480
|
+
// InstantCMS 2. system/hooks/${name}/api.hooks.php
|
|
481
|
+
|
|
482
|
+
class on${Name}ApiHook {
|
|
483
|
+
public function onBeforeRequest($client, $method, $path, $params) {
|
|
484
|
+
return [$client, $method, $path, $params];
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
public function onAfterRequest($result) {
|
|
488
|
+
return $result;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
public function onRequestError($error) {
|
|
492
|
+
cmsUser::addSessionMessage('Ошибка API: ' . $error->getMessage(), 'error');
|
|
493
|
+
return false;
|
|
494
|
+
}
|
|
495
|
+
}`;
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Generates a complete external API client for an InstantCMS addon
|
|
499
|
+
*
|
|
500
|
+
* @param opts - Configuration options for the API client
|
|
501
|
+
* @returns Object containing generated files and metadata
|
|
502
|
+
*
|
|
503
|
+
* @example
|
|
504
|
+
* ```typescript
|
|
505
|
+
* const result = scaffoldExternalApi({
|
|
506
|
+
* addon_name: 'payment_gateway',
|
|
507
|
+
* base_url: 'https://api.payment.example.com/v1',
|
|
508
|
+
* endpoints: [
|
|
509
|
+
* { path: '/payments', method: 'POST', description: 'Создать платёж' },
|
|
510
|
+
* { path: '/payments/{id}', method: 'GET', description: 'Статус платежа' }
|
|
511
|
+
* ],
|
|
512
|
+
* options: { use_auth: true, auth_type: 'bearer', use_rate_limit: true }
|
|
513
|
+
* });
|
|
514
|
+
* ```
|
|
515
|
+
*/
|
|
516
|
+
function scaffoldExternalApi(opts) {
|
|
517
|
+
const { lowercase, UpperCamelCase } = (0, scaffold_1.normalizeAddonName)(opts.addon_name);
|
|
518
|
+
const files = {};
|
|
519
|
+
const options = {
|
|
520
|
+
use_auth: opts.options?.use_auth ?? true,
|
|
521
|
+
auth_type: opts.options?.auth_type ?? 'bearer',
|
|
522
|
+
timeout: opts.options?.timeout ?? 30,
|
|
523
|
+
use_rate_limit: opts.options?.use_rate_limit ?? true,
|
|
524
|
+
rate_limit: opts.options?.rate_limit ?? 60,
|
|
525
|
+
use_cache: opts.options?.use_cache ?? false,
|
|
526
|
+
};
|
|
527
|
+
files[`${lowercase}/api/client.php`] = generateApiClient(lowercase, UpperCamelCase, opts.base_url, opts.endpoints, options);
|
|
528
|
+
files[`${lowercase}/api/request.php`] = generateApiRequest(lowercase, UpperCamelCase, options);
|
|
529
|
+
if (options.use_auth) {
|
|
530
|
+
files[`${lowercase}/api/auth.php`] = generateApiAuth(lowercase, UpperCamelCase, options);
|
|
531
|
+
}
|
|
532
|
+
if (options.use_rate_limit) {
|
|
533
|
+
files[`${lowercase}/api/rate_limiter.php`] = generateRateLimiter(lowercase, UpperCamelCase, options);
|
|
534
|
+
}
|
|
535
|
+
if (options.use_cache) {
|
|
536
|
+
files[`${lowercase}/api/cache.php`] = generateApiCache(lowercase, UpperCamelCase, options);
|
|
537
|
+
}
|
|
538
|
+
files[`system/hooks/${lowercase}/api.hooks.php`] = generateApiHooks(lowercase, UpperCamelCase, opts.endpoints, options);
|
|
539
|
+
return {
|
|
540
|
+
addon_name: lowercase,
|
|
541
|
+
files,
|
|
542
|
+
base_url: opts.base_url,
|
|
543
|
+
endpoints_count: opts.endpoints.length,
|
|
544
|
+
options,
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
exports.externalApiToolSchema = {
|
|
548
|
+
name: 'scaffold_external_api',
|
|
549
|
+
description: 'Генерация клиента для внешнего API с поддержкой авторизации и rate limiting',
|
|
550
|
+
inputSchema: {
|
|
551
|
+
type: 'object',
|
|
552
|
+
properties: {
|
|
553
|
+
addon_name: { type: 'string', description: 'Имя дополнения' },
|
|
554
|
+
base_url: { type: 'string', description: 'Базовый URL API' },
|
|
555
|
+
endpoints: {
|
|
556
|
+
type: 'array',
|
|
557
|
+
items: {
|
|
558
|
+
type: 'object',
|
|
559
|
+
properties: {
|
|
560
|
+
path: { type: 'string' },
|
|
561
|
+
method: { type: 'string' },
|
|
562
|
+
description: { type: 'string' },
|
|
563
|
+
},
|
|
564
|
+
},
|
|
565
|
+
description: 'Эндпоинты API',
|
|
566
|
+
},
|
|
567
|
+
options: {
|
|
568
|
+
type: 'object',
|
|
569
|
+
properties: {
|
|
570
|
+
use_auth: { type: 'boolean', description: 'Использовать авторизацию' },
|
|
571
|
+
auth_type: {
|
|
572
|
+
type: 'string',
|
|
573
|
+
enum: ['api_key', 'bearer', 'basic', 'oauth2'],
|
|
574
|
+
description: 'Тип авторизации',
|
|
575
|
+
},
|
|
576
|
+
timeout: { type: 'number', description: 'Таймаут запроса' },
|
|
577
|
+
use_rate_limit: { type: 'boolean', description: 'Ограничение запросов' },
|
|
578
|
+
rate_limit: { type: 'number', description: 'Макс. запросов в минуту' },
|
|
579
|
+
use_cache: { type: 'boolean', description: 'Кэширование ответов' },
|
|
580
|
+
},
|
|
581
|
+
},
|
|
582
|
+
},
|
|
583
|
+
required: ['addon_name', 'base_url', 'endpoints'],
|
|
584
|
+
},
|
|
585
|
+
inputExamples: [
|
|
586
|
+
{
|
|
587
|
+
addon_name: 'payment_gateway',
|
|
588
|
+
base_url: 'https://api.payment.example.com/v1',
|
|
589
|
+
endpoints: [
|
|
590
|
+
{ path: '/payments', method: 'POST', description: 'Создать платёж' },
|
|
591
|
+
{ path: '/payments/{id}', method: 'GET', description: 'Статус платежа' },
|
|
592
|
+
],
|
|
593
|
+
options: { use_auth: true, auth_type: 'bearer', use_rate_limit: true },
|
|
594
|
+
},
|
|
595
|
+
],
|
|
596
|
+
};
|