@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.
Files changed (111) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +308 -0
  3. package/dist/__tests__/artifact-tool.test.js +196 -0
  4. package/dist/__tests__/db-tool.test.js +152 -0
  5. package/dist/__tests__/define-tool.test.js +106 -0
  6. package/dist/__tests__/find-tool.test.js +185 -0
  7. package/dist/__tests__/get-component-api.test.js +144 -0
  8. package/dist/__tests__/hardening.test.js +73 -0
  9. package/dist/__tests__/hooks-tool.test.js +173 -0
  10. package/dist/__tests__/mcp-integration-extra.test.js +166 -0
  11. package/dist/__tests__/mcp-integration.test.js +53 -0
  12. package/dist/__tests__/pagination.test.js +114 -0
  13. package/dist/__tests__/performance-baseline.test.js +56 -0
  14. package/dist/__tests__/project-patch.test.js +144 -0
  15. package/dist/__tests__/project-source.test.js +44 -0
  16. package/dist/__tests__/project-workflows-extra.test.js +165 -0
  17. package/dist/__tests__/project-workflows.test.js +51 -0
  18. package/dist/__tests__/scaffold-addon-roundtrip.test.js +287 -0
  19. package/dist/__tests__/serialization.test.js +170 -0
  20. package/dist/__tests__/source-knowledge-parsers.test.js +42 -0
  21. package/dist/__tests__/template-development.test.js +78 -0
  22. package/dist/__tests__/template-productivity.test.js +87 -0
  23. package/dist/__tests__/tools.test.js +2143 -0
  24. package/dist/data/components.js +2818 -0
  25. package/dist/data/controllers-map.js +7571 -0
  26. package/dist/data/core-api.js +9356 -0
  27. package/dist/data/database-schema.js +4555 -0
  28. package/dist/data/events-map.js +1339 -0
  29. package/dist/data/fields-map.js +3692 -0
  30. package/dist/data/hooks.js +2257 -0
  31. package/dist/data/js-api.js +123 -0
  32. package/dist/data/libs-api.js +264 -0
  33. package/dist/data/routes-map.js +203 -0
  34. package/dist/data/schemas-validation.js +255 -0
  35. package/dist/data/schemas.js +1404 -0
  36. package/dist/data/traits-map.js +1004 -0
  37. package/dist/data/version-profiles.js +41 -0
  38. package/dist/data/widgets-map.js +131 -0
  39. package/dist/data/wysiwyg-map.js +486 -0
  40. package/dist/generated/components-source.js +14748 -0
  41. package/dist/generated/hooks-source.js +3399 -0
  42. package/dist/generated/knowledge-meta.js +101 -0
  43. package/dist/index.js +13 -0
  44. package/dist/registry/database-tools.js +112 -0
  45. package/dist/registry/extension-tools.js +473 -0
  46. package/dist/registry/generator-tools.js +506 -0
  47. package/dist/registry/knowledge-tools.js +441 -0
  48. package/dist/registry/language-tools.js +99 -0
  49. package/dist/registry/meta-tools.js +152 -0
  50. package/dist/registry/project-tools.js +48 -0
  51. package/dist/registry/resources.js +98 -0
  52. package/dist/registry/source-tools.js +211 -0
  53. package/dist/registry/template-development-tools.js +79 -0
  54. package/dist/server.js +32 -0
  55. package/dist/tools/addon-tool.js +1437 -0
  56. package/dist/tools/admin-partial-tool.js +498 -0
  57. package/dist/tools/api-tool.js +294 -0
  58. package/dist/tools/artifact-tool.js +95 -0
  59. package/dist/tools/cache-tool.js +360 -0
  60. package/dist/tools/component-tool.js +415 -0
  61. package/dist/tools/controllers-tool.js +125 -0
  62. package/dist/tools/cron-tool.js +197 -0
  63. package/dist/tools/crud-tool.js +659 -0
  64. package/dist/tools/db-tool.js +198 -0
  65. package/dist/tools/email-tool.js +222 -0
  66. package/dist/tools/external-api-tool.js +596 -0
  67. package/dist/tools/filter-tool.js +341 -0
  68. package/dist/tools/form-tool.js +275 -0
  69. package/dist/tools/grid-tool.js +189 -0
  70. package/dist/tools/hooks-tool.js +104 -0
  71. package/dist/tools/import-export-tool.js +548 -0
  72. package/dist/tools/lang-tool.js +251 -0
  73. package/dist/tools/layout-override-tool.js +125 -0
  74. package/dist/tools/layout-tool.js +548 -0
  75. package/dist/tools/maria-tool.js +127 -0
  76. package/dist/tools/mariadb.js +201 -0
  77. package/dist/tools/migration-tool.js +329 -0
  78. package/dist/tools/oauth-tool.js +520 -0
  79. package/dist/tools/parser/components-parser.js +76 -0
  80. package/dist/tools/parser/controllers-parser.js +313 -0
  81. package/dist/tools/parser/core-parser.js +294 -0
  82. package/dist/tools/parser/coverage-generator.js +424 -0
  83. package/dist/tools/parser/events-parser.js +127 -0
  84. package/dist/tools/parser/fields-parser.js +382 -0
  85. package/dist/tools/parser/hooks-parser.js +106 -0
  86. package/dist/tools/parser/sql-parser.js +197 -0
  87. package/dist/tools/parser/traits-parser.js +161 -0
  88. package/dist/tools/parser/widgets-parser.js +150 -0
  89. package/dist/tools/permission-tool.js +348 -0
  90. package/dist/tools/project-patch-tool.js +73 -0
  91. package/dist/tools/project-source-tool.js +188 -0
  92. package/dist/tools/project-workflow-tool.js +186 -0
  93. package/dist/tools/requirement-tool.js +212 -0
  94. package/dist/tools/scaffold-tool.js +815 -0
  95. package/dist/tools/seo-tool.js +412 -0
  96. package/dist/tools/source-tool.js +155 -0
  97. package/dist/tools/template-development-tool.js +271 -0
  98. package/dist/tools/template-overrides-tool.js +294 -0
  99. package/dist/tools/template-productivity-tool.js +319 -0
  100. package/dist/tools/template-tool.js +665 -0
  101. package/dist/tools/test-tool.js +183 -0
  102. package/dist/tools/webhook-tool.js +427 -0
  103. package/dist/tools/widget-tool.js +331 -0
  104. package/dist/tools/wysiwyg-tool.js +137 -0
  105. package/dist/types/scaffold.js +68 -0
  106. package/dist/utils/define-tool.js +37 -0
  107. package/dist/utils/find-tool.js +97 -0
  108. package/dist/utils/mcp-result.js +18 -0
  109. package/dist/utils/pagination.js +27 -0
  110. package/dist/utils/serialization.js +27 -0
  111. package/package.json +94 -0
@@ -0,0 +1,183 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.scaffoldTest = scaffoldTest;
4
+ function scaffoldTest(opts) {
5
+ const name = opts.addon_name.toLowerCase().replace(/[^a-z0-9_]/g, '_');
6
+ const Name = opts.class_name || name.split('_').map(capitalize).join('');
7
+ const classType = opts.class_type || 'model';
8
+ const files = {};
9
+ if (opts.options?.test_framework === 'codeception') {
10
+ files[`tests/unit/${name}/${Name}Test.php`] = generateCodeceptionTest(name, Name, classType, opts.methods, opts.options);
11
+ files[`tests/unit.suite.yml`] = generateCodeceptionSuite(name);
12
+ }
13
+ else {
14
+ files[`tests/phpunit/${Name}Test.php`] = generatePhpUnitTest(name, Name, classType, opts.methods, opts.options);
15
+ files[`phpunit.xml`] = generatePhpUnitConfig(name);
16
+ }
17
+ return {
18
+ addon_name: name,
19
+ class_name: Name,
20
+ class_type: classType,
21
+ test_framework: opts.options?.test_framework || 'phpunit',
22
+ files,
23
+ methods_count: opts.methods.length,
24
+ structure_notes: [
25
+ `Тесты: ${Object.keys(files).join(', ')}`,
26
+ `Запуск: ./vendor/bin/phpunit или ./codecept run unit`,
27
+ ],
28
+ };
29
+ }
30
+ function capitalize(str) {
31
+ return str.charAt(0).toUpperCase() + str.slice(1);
32
+ }
33
+ function generatePhpUnitTest(name, Name, classType, methods, options) {
34
+ const mockDb = options?.mock_db ?? true;
35
+ const mockCache = options?.mock_cache ?? true;
36
+ let code = `<?php
37
+
38
+ use PHPUnit\\Framework\\TestCase;
39
+
40
+ class ${Name}Test extends TestCase {
41
+
42
+ `;
43
+ if (mockDb) {
44
+ code += ` protected $db;
45
+
46
+ protected function setUp(): void {
47
+ parent::setUp();
48
+ $this->db = $this->createMock(\\icms\\db\\DatabaseConnection::class);
49
+ }
50
+
51
+ protected function tearDown(): void {
52
+ unset($this->db);
53
+ parent::tearDown();
54
+ }
55
+
56
+ `;
57
+ }
58
+ if (mockCache) {
59
+ code += ` protected $cache;
60
+
61
+ protected function setUp(): void {
62
+ parent::setUp();
63
+ $this->cache = $this->createMock(\\icms\\cache\\Cache::class);
64
+ }
65
+
66
+ `;
67
+ }
68
+ for (const method of methods) {
69
+ code += generateMethodTest(name, Name, classType, method);
70
+ }
71
+ code += `}
72
+ `;
73
+ return code;
74
+ }
75
+ function generateMethodTest(name, Name, classType, method) {
76
+ const methodName = method.replace(/[^a-zA-Z0-9_]/g, '_');
77
+ let code = `
78
+ /**
79
+ * @covers ${Name}::${method}
80
+ */
81
+ public function test${capitalize(methodName)}() {
82
+ `;
83
+ switch (classType) {
84
+ case 'model':
85
+ code += ` $model = new model${Name}();
86
+ $this->assertInstanceOf(\\cmsModel::class, $model);
87
+ `;
88
+ break;
89
+ case 'controller':
90
+ code += ` $controller = $this->createMock(\\cmsController::class);
91
+ $this->assertInstanceOf(\\cmsController::class, $controller);
92
+ `;
93
+ break;
94
+ case 'component':
95
+ code += ` $component = new ${Name}($this->request);
96
+ $this->assertTrue(method_exists($component, '${method}'));
97
+ `;
98
+ break;
99
+ case 'widget':
100
+ code += ` $widget = new ${Name}();
101
+ $this->assertInstanceOf(\\cmsWidget::class, $widget);
102
+ `;
103
+ break;
104
+ default:
105
+ code += ` // Test for ${classType}::${method}
106
+ $this->assertTrue(true);
107
+ `;
108
+ }
109
+ code += ` }
110
+
111
+ `;
112
+ return code;
113
+ }
114
+ function generatePhpUnitConfig(name) {
115
+ return `<?xml version="1.0" encoding="UTF-8"?>
116
+ <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
117
+ xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
118
+ bootstrap="vendor/autoload.php"
119
+ colors="true"
120
+ cacheDirectory=".phpunit.cache"
121
+ >
122
+ <testsuites>
123
+ <testsuite name="${name}">
124
+ <directory>tests/phpunit</directory>
125
+ </testsuite>
126
+ </testsuites>
127
+ <coverage>
128
+ <include>
129
+ <directory suffix=".php">system/controllers/${name}</directory>
130
+ </include>
131
+ </coverage>
132
+ </phpunit>
133
+ `;
134
+ }
135
+ function generateCodeceptionTest(name, Name, classType, methods, options) {
136
+ const mockDb = options?.mock_db ?? true;
137
+ let code = `<?php
138
+
139
+ namespace tests\\unit\\${name};
140
+
141
+ use Codeception\\Test\\Unit;
142
+
143
+ class ${Name}Test extends Unit {
144
+
145
+ `;
146
+ if (mockDb) {
147
+ code += ` /** @var \\UnitTester */
148
+ protected $tester;
149
+
150
+ protected function _before() {
151
+ parent::_before();
152
+ }
153
+
154
+ protected function _after() {
155
+ unset($this->tester);
156
+ parent::_after();
157
+ }
158
+
159
+ `;
160
+ }
161
+ for (const method of methods) {
162
+ code += `
163
+ /**
164
+ * @covers ${Name}::${method}
165
+ */
166
+ public function test${capitalize(method)}($I) {
167
+ $I->assertTrue(true);
168
+ }
169
+ `;
170
+ }
171
+ code += `
172
+ }
173
+ `;
174
+ return code;
175
+ }
176
+ function generateCodeceptionSuite(_name) {
177
+ return `actor: UnitTester
178
+ modules:
179
+ enabled:
180
+ - Asserts
181
+ - \\Helper\\Unit
182
+ `;
183
+ }
@@ -0,0 +1,427 @@
1
+ "use strict";
2
+ /**
3
+ * @fileoverview Webhook scaffolding tool for InstantCMS
4
+ * Generates webhook handlers, queue processing, and security features
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.webhookToolSchema = void 0;
8
+ exports.scaffoldWebhook = scaffoldWebhook;
9
+ const scaffold_1 = require("../types/scaffold");
10
+ /**
11
+ * Generates webhook handler class
12
+ */
13
+ function generateWebhookHandler(name, Name, events, options) {
14
+ const eventHandlers = events
15
+ .map(event => {
16
+ const eventName = event.replace('.', '_');
17
+ return ` '${event}' => 'handle${eventName}',`;
18
+ })
19
+ .join('\n');
20
+ const eventMethods = events
21
+ .map(event => {
22
+ const eventName = event.replace('.', '_');
23
+ return ` private function handle${eventName}($data) {
24
+ return ['processed' => true, 'event' => '${event}'];
25
+ }`;
26
+ })
27
+ .join('\n\n');
28
+ return `<?php
29
+ // InstantCMS 2. ${name}/webhooks.php
30
+
31
+ class ${Name}Webhook {
32
+ private $config = [];
33
+ private $logger = null;
34
+
35
+ public function __construct() {
36
+ $config_path = cmsConfig::get('root_path') . '/system/config/webhooks/${name}.php';
37
+ if (file_exists($config_path)) {
38
+ $this->config = include $config_path;
39
+ }
40
+ $this->logger = ${Name}WebhookLogger::getInstance();
41
+ }
42
+
43
+ public function handle($event, $data) {
44
+ if (!in_array($event, $this->config['events'])) {
45
+ return ['success' => false, 'error' => 'Event not configured'];
46
+ }
47
+
48
+ $this->logger->log('webhook_received', [
49
+ 'event' => $event,
50
+ 'data' => $data,
51
+ 'timestamp' => date('Y-m-d H:i:s'),
52
+ ]);
53
+
54
+ $handlers = [
55
+ ${eventHandlers}
56
+ ];
57
+
58
+ if (!isset($handlers[$event])) {
59
+ return ['success' => false, 'error' => 'No handler for event'];
60
+ }
61
+
62
+ try {
63
+ $result = $this->{$handlers[$event]}($data);
64
+
65
+ $this->logger->log('webhook_handled', [
66
+ 'event' => $event,
67
+ 'result' => $result,
68
+ ]);
69
+
70
+ return ['success' => true, 'result' => $result];
71
+ } catch (Exception $e) {
72
+ $this->logger->log('webhook_error', [
73
+ 'event' => $event,
74
+ 'error' => $e->getMessage(),
75
+ ]);
76
+
77
+ if (${options.use_retry}) {
78
+ $this->scheduleRetry($event, $data, $e->getMessage());
79
+ }
80
+
81
+ return ['success' => false, 'error' => $e->getMessage()];
82
+ }
83
+ }
84
+
85
+ ${eventMethods}
86
+
87
+ private function scheduleRetry($event, $data, $error) {
88
+ $queue = new ${Name}WebhookQueue();
89
+ $queue->add([
90
+ 'event' => $event,
91
+ 'data' => $data,
92
+ 'error' => $error,
93
+ 'attempt' => 0,
94
+ 'max_attempts' => ${options.retry_count},
95
+ 'scheduled_at' => date('Y-m-d H:i:s'),
96
+ ]);
97
+ }
98
+
99
+ public function processQueue() {
100
+ $queue = new ${Name}WebhookQueue();
101
+ $pending = $queue->getPending();
102
+
103
+ foreach ($pending as $item) {
104
+ $this->processQueueItem($item);
105
+ }
106
+
107
+ return ['processed' => count($pending)];
108
+ }
109
+
110
+ private function processQueueItem($item) {
111
+ $result = $this->handle($item['event'], $item['data']);
112
+
113
+ if ($result['success']) {
114
+ $item['status'] = 'completed';
115
+ $item['completed_at'] = date('Y-m-d H:i:s');
116
+ } else {
117
+ $item['attempt']++;
118
+ if ($item['attempt'] >= $item['max_attempts']) {
119
+ $item['status'] = 'failed';
120
+ $item['failed_at'] = date('Y-m-d H:i:s');
121
+ } else {
122
+ $item['scheduled_at'] = date('Y-m-d H:i:s', strtotime('+5 minutes'));
123
+ }
124
+ }
125
+
126
+ $queue = new ${Name}WebhookQueue();
127
+ $queue->update($item);
128
+ }
129
+
130
+ public static function receive($request) {
131
+ $webhook = new self();
132
+
133
+ $event = $request->get('event', '');
134
+ $data = $request->getAll();
135
+
136
+ unset($data['event']);
137
+
138
+ return $webhook->handle($event, $data);
139
+ }
140
+ }`;
141
+ }
142
+ /**
143
+ * Generates webhook configuration file
144
+ */
145
+ function generateWebhookConfig(name, _Name, events, options) {
146
+ return `<?php
147
+ // InstantCMS 2. system/config/webhooks/${name}.php
148
+
149
+ return [
150
+ 'addon' => '${name}',
151
+ 'events' => ['${events.join("', '")}'],
152
+ 'endpoints' => [
153
+ [
154
+ 'url' => '/webhooks/${name}',
155
+ 'method' => 'POST',
156
+ 'active' => true,
157
+ ],
158
+ ],
159
+ 'options' => [
160
+ 'signature' => ${options.use_signature},
161
+ 'retry' => ${options.use_retry},
162
+ 'retry_count' => ${options.retry_count},
163
+ 'async' => ${options.async_execution},
164
+ 'secret' => '${name}_webhook_secret_' . md5(cmsConfig::get('secret')),
165
+ ],
166
+ 'handlers' => [
167
+ ${events.map(e => ` '${e}' => true,`).join('\n')}
168
+ ],
169
+ ];`;
170
+ }
171
+ /**
172
+ * Generates webhook hook handlers
173
+ */
174
+ function generateWebhookHooks(name, Name, events, _options) {
175
+ const eventHooks = events
176
+ .map(event => {
177
+ return ` public function on${Name}${event.replace('.', '')}() {
178
+ $webhook = new ${Name}Webhook();
179
+ return $webhook->handle('${event}', func_get_args());
180
+ }`;
181
+ })
182
+ .join('\n\n');
183
+ return `<?php
184
+ // InstantCMS 2. system/hooks/${name}/webhook.hooks.php
185
+
186
+ class on${Name}WebhookHook {
187
+ ${eventHooks}
188
+
189
+ public function onCronRun() {
190
+ $webhook = new ${Name}Webhook();
191
+ return $webhook->processQueue();
192
+ }
193
+
194
+ public function onAfterSave($item) {
195
+ $webhook = new ${Name}Webhook();
196
+ $webhook->handle('item.created', $item);
197
+ return true;
198
+ }
199
+
200
+ public function onAfterUpdate($item) {
201
+ $webhook = new ${Name}Webhook();
202
+ $webhook->handle('item.updated', $item);
203
+ return true;
204
+ }
205
+
206
+ public function onAfterDelete($item) {
207
+ $webhook = new ${Name}Webhook();
208
+ $webhook->handle('item.deleted', $item);
209
+ return true;
210
+ }
211
+ }`;
212
+ }
213
+ /**
214
+ * Generates webhook queue class
215
+ */
216
+ function generateWebhookQueue(name, Name, _events, _options) {
217
+ return `<?php
218
+ // InstantCMS 2. ${name}/webhook.queue.php
219
+
220
+ class ${Name}WebhookQueue {
221
+ private $table = '${name}_webhook_queue';
222
+ private $db = null;
223
+
224
+ public function __construct() {
225
+ $this->db = cmsDatabase::getInstance();
226
+ }
227
+
228
+ public function add($item) {
229
+ $item['created_at'] = date('Y-m-d H:i:s');
230
+ $item['status'] = 'pending';
231
+
232
+ return $this->db->insert($this->table, $item);
233
+ }
234
+
235
+ public function getPending($limit = 100) {
236
+ return $this->db->get('${name}_webhook_queue', function ($item) {
237
+ return $item;
238
+ }, [
239
+ 'status' => 'pending',
240
+ 'scheduled_at <=' => date('Y-m-d H:i:s'),
241
+ ], 'priority ASC, created_at ASC', 1, $limit);
242
+ }
243
+
244
+ public function getFailed($limit = 100) {
245
+ return $this->db->get('${name}_webhook_queue', function ($item) {
246
+ return $item;
247
+ }, [
248
+ 'status' => 'failed',
249
+ ], 'created_at DESC', 1, $limit);
250
+ }
251
+
252
+ public function update($item) {
253
+ return $this->db->update($this->table, $item['id'], $item);
254
+ }
255
+
256
+ public function delete($id) {
257
+ return $this->db->delete($this->table, $id);
258
+ }
259
+
260
+ public function getStats() {
261
+ return [
262
+ 'pending' => $this->db->getCount($this->table, ['status' => 'pending']),
263
+ 'completed' => $this->db->getCount($this->table, ['status' => 'completed']),
264
+ 'failed' => $this->db->getCount($this->table, ['status' => 'failed']),
265
+ ];
266
+ }
267
+
268
+ public function retry($id) {
269
+ $item = $this->db->getItem($this->table, $id);
270
+ if (!$item) {
271
+ return false;
272
+ }
273
+
274
+ $item['status'] = 'pending';
275
+ $item['attempt'] = 0;
276
+ $item['scheduled_at'] = date('Y-m-d H:i:s');
277
+
278
+ return $this->update($item);
279
+ }
280
+
281
+ public function purge($days = 30) {
282
+ $before = date('Y-m-d H:i:s', strtotime("-{$days} days"));
283
+ return $this->db->query("DELETE FROM {$this->table} WHERE status IN ('completed', 'failed') AND created_at < '{$before}'");
284
+ }
285
+ }`;
286
+ }
287
+ /**
288
+ * Generates webhook security class
289
+ */
290
+ function generateWebhookSecurity(name, Name, _options) {
291
+ return `<?php
292
+ // InstantCMS 2. ${name}/webhook.security.php
293
+
294
+ class ${Name}WebhookSecurity {
295
+ private $secret = '';
296
+
297
+ public function __construct() {
298
+ $config = include cmsConfig::get('root_path') . '/system/config/webhooks/${name}.php';
299
+ $this->secret = $config['options']['secret'] ?? '';
300
+ }
301
+
302
+ public function verifySignature($payload, $signature, $timestamp = null) {
303
+ if (empty($this->secret)) {
304
+ return false;
305
+ }
306
+
307
+ if ($timestamp !== null) {
308
+ $tolerance = 300;
309
+ if (abs(time() - $timestamp) > $tolerance) {
310
+ return false;
311
+ }
312
+ }
313
+
314
+ $expected = $this->generateSignature($payload, $timestamp);
315
+
316
+ return hash_equals($expected, $signature);
317
+ }
318
+
319
+ public function generateSignature($payload, $timestamp = null) {
320
+ $timestamp = $timestamp ?? time();
321
+
322
+ if (is_array($payload)) {
323
+ $payload = json_encode($payload);
324
+ }
325
+
326
+ $signed_payload = $timestamp . '.' . $payload;
327
+
328
+ return hash_hmac('sha256', $signed_payload, $this->secret);
329
+ }
330
+
331
+ public function generateHeaders($payload) {
332
+ $timestamp = time();
333
+ $signature = $this->generateSignature($payload, $timestamp);
334
+
335
+ return [
336
+ 'X-Webhook-Signature' => $signature,
337
+ 'X-Webhook-Timestamp' => $timestamp,
338
+ 'X-Webhook-Event' => '${name}',
339
+ ];
340
+ }
341
+
342
+ public function verifyRequest($request) {
343
+ $signature = $request->get('HTTP_X_WEBHOOK_SIGNATURE', '');
344
+ $timestamp = $request->get('HTTP_X_WEBHOOK_TIMESTAMP', 0);
345
+ $payload = $request->getRawData();
346
+
347
+ if (empty($payload)) {
348
+ return ['valid' => false, 'error' => 'Empty payload'];
349
+ }
350
+
351
+ if (!$this->verifySignature($payload, $signature, $timestamp)) {
352
+ return ['valid' => false, 'error' => 'Invalid signature'];
353
+ }
354
+
355
+ return ['valid' => true];
356
+ }
357
+ }`;
358
+ }
359
+ /**
360
+ * Generates a complete webhook system for an InstantCMS addon
361
+ *
362
+ * @param opts - Configuration options for the webhook system
363
+ * @returns Object containing generated files and metadata
364
+ *
365
+ * @example
366
+ * ```typescript
367
+ * const result = scaffoldWebhook({
368
+ * addon_name: 'shop',
369
+ * events: ['order.created', 'order.paid', 'order.cancelled'],
370
+ * options: { use_signature: true, use_retry: true }
371
+ * });
372
+ * ```
373
+ */
374
+ function scaffoldWebhook(opts) {
375
+ const { lowercase, UpperCamelCase } = (0, scaffold_1.normalizeAddonName)(opts.addon_name);
376
+ const files = {};
377
+ const events = opts.events || ['item.created', 'item.updated', 'item.deleted'];
378
+ const options = {
379
+ use_signature: opts.options?.use_signature ?? true,
380
+ use_retry: opts.options?.use_retry ?? true,
381
+ retry_count: opts.options?.retry_count ?? 3,
382
+ async_execution: opts.options?.async_execution ?? false,
383
+ };
384
+ files[`${lowercase}/webhooks.php`] = generateWebhookHandler(lowercase, UpperCamelCase, events, options);
385
+ files[`${lowercase}/webhook.config.php`] = generateWebhookConfig(lowercase, UpperCamelCase, events, options);
386
+ files[`system/hooks/${lowercase}/webhook.hooks.php`] = generateWebhookHooks(lowercase, UpperCamelCase, events, options);
387
+ if (options.use_retry) {
388
+ files[`${lowercase}/webhook.queue.php`] = generateWebhookQueue(lowercase, UpperCamelCase, events, options);
389
+ }
390
+ if (options.use_signature) {
391
+ files[`${lowercase}/webhook.security.php`] = generateWebhookSecurity(lowercase, UpperCamelCase, options);
392
+ }
393
+ return {
394
+ addon_name: lowercase,
395
+ files,
396
+ events_count: events.length,
397
+ options,
398
+ };
399
+ }
400
+ exports.webhookToolSchema = {
401
+ name: 'scaffold_webhook',
402
+ description: 'Генерация системы веб-хуков для InstantCMS',
403
+ inputSchema: {
404
+ type: 'object',
405
+ properties: {
406
+ addon_name: { type: 'string', description: 'Имя дополнения' },
407
+ events: { type: 'array', items: { type: 'string' }, description: 'События для обработки' },
408
+ options: {
409
+ type: 'object',
410
+ properties: {
411
+ use_signature: { type: 'boolean', description: 'Проверка подписи' },
412
+ use_retry: { type: 'boolean', description: 'Повтор при ошибках' },
413
+ retry_count: { type: 'number', description: 'Количество попыток' },
414
+ async_execution: { type: 'boolean', description: 'Асинхронное выполнение' },
415
+ },
416
+ },
417
+ },
418
+ required: ['addon_name', 'events'],
419
+ },
420
+ inputExamples: [
421
+ {
422
+ addon_name: 'shop',
423
+ events: ['order.created', 'order.paid', 'order.cancelled'],
424
+ options: { use_signature: true, use_retry: true },
425
+ },
426
+ ],
427
+ };