driftdetect-dashboard 0.8.1 → 0.8.2
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 +121 -0
- package/dist/server/api-routes.d.ts +50 -0
- package/dist/server/api-routes.js +652 -0
- package/dist/server/api-routes.js.map +1 -0
- package/dist/server/dashboard-server.d.ts +64 -0
- package/dist/server/dashboard-server.js +154 -0
- package/dist/server/dashboard-server.js.map +1 -0
- package/dist/server/drift-data-reader.d.ts +522 -0
- package/dist/server/drift-data-reader.js +1550 -0
- package/dist/server/drift-data-reader.js.map +1 -0
- package/dist/server/express-app.d.ts +24 -0
- package/dist/server/express-app.js +74 -0
- package/dist/server/express-app.js.map +1 -0
- package/dist/server/galaxy-data-transformer.d.ts +178 -0
- package/dist/server/galaxy-data-transformer.js +562 -0
- package/dist/server/galaxy-data-transformer.js.map +1 -0
- package/dist/server/index.d.ts +20 -0
- package/dist/server/index.js +14 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/pattern-watcher.d.ts +55 -0
- package/dist/server/pattern-watcher.d.ts.map +1 -0
- package/dist/server/pattern-watcher.js +157 -0
- package/dist/server/pattern-watcher.js.map +1 -0
- package/dist/server/quality-gates-api.d.ts +12 -0
- package/dist/server/quality-gates-api.js +226 -0
- package/dist/server/quality-gates-api.js.map +1 -0
- package/dist/server/websocket-server.d.ts +83 -0
- package/dist/server/websocket-server.js +189 -0
- package/dist/server/websocket-server.js.map +1 -0
- package/package.json +20 -20
|
@@ -0,0 +1,652 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Routes for the Drift Dashboard
|
|
3
|
+
*
|
|
4
|
+
* Provides REST API endpoints for patterns, violations, files, stats, and config.
|
|
5
|
+
*
|
|
6
|
+
* @requirements 8.1 - GET `/api/patterns` to list all patterns
|
|
7
|
+
* @requirements 8.2 - GET `/api/patterns/:id` to get pattern details with locations
|
|
8
|
+
* @requirements 8.3 - POST `/api/patterns/:id/approve` to approve a pattern
|
|
9
|
+
* @requirements 8.4 - POST `/api/patterns/:id/ignore` to ignore a pattern
|
|
10
|
+
* @requirements 8.5 - DELETE `/api/patterns/:id` to delete a pattern
|
|
11
|
+
* @requirements 8.6 - GET `/api/violations` to list all violations
|
|
12
|
+
* @requirements 8.7 - GET `/api/files` to get the file tree
|
|
13
|
+
* @requirements 8.8 - GET `/api/files/:path` to get patterns and violations for a specific file
|
|
14
|
+
* @requirements 8.9 - GET `/api/stats` to get overview statistics
|
|
15
|
+
* @requirements 8.10 - GET `/api/config` to get configuration
|
|
16
|
+
* @requirements 8.11 - PUT `/api/config` to update configuration
|
|
17
|
+
* @requirements 8.12 - Return appropriate HTTP status codes and error messages
|
|
18
|
+
*/
|
|
19
|
+
import { Router } from 'express';
|
|
20
|
+
import { DriftDataReader } from './drift-data-reader.js';
|
|
21
|
+
import { createGalaxyDataTransformer } from './galaxy-data-transformer.js';
|
|
22
|
+
import { getProjectRegistry } from 'driftdetect-core';
|
|
23
|
+
import { handleQualityGatesRequest } from './quality-gates-api.js';
|
|
24
|
+
// ============================================================================
|
|
25
|
+
// Error Classes
|
|
26
|
+
// ============================================================================
|
|
27
|
+
export class NotFoundError extends Error {
|
|
28
|
+
statusCode = 404;
|
|
29
|
+
constructor(message) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.name = 'NotFoundError';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export class BadRequestError extends Error {
|
|
35
|
+
statusCode = 400;
|
|
36
|
+
constructor(message) {
|
|
37
|
+
super(message);
|
|
38
|
+
this.name = 'BadRequestError';
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export class InternalServerError extends Error {
|
|
42
|
+
statusCode = 500;
|
|
43
|
+
constructor(message) {
|
|
44
|
+
super(message);
|
|
45
|
+
this.name = 'InternalServerError';
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// ============================================================================
|
|
49
|
+
// API Routes Factory
|
|
50
|
+
// ============================================================================
|
|
51
|
+
/**
|
|
52
|
+
* Create API routes for the dashboard
|
|
53
|
+
* @param reader - DriftDataReader instance for accessing drift data
|
|
54
|
+
*/
|
|
55
|
+
export function createApiRoutes(reader) {
|
|
56
|
+
const router = Router();
|
|
57
|
+
// ==========================================================================
|
|
58
|
+
// Pattern Routes
|
|
59
|
+
// ==========================================================================
|
|
60
|
+
/**
|
|
61
|
+
* GET /api/patterns - List all patterns with optional filters
|
|
62
|
+
* @requirements 8.1
|
|
63
|
+
*/
|
|
64
|
+
router.get('/patterns', async (req, res, next) => {
|
|
65
|
+
try {
|
|
66
|
+
const query = {};
|
|
67
|
+
if (req.query['category']) {
|
|
68
|
+
query.category = req.query['category'];
|
|
69
|
+
}
|
|
70
|
+
if (req.query['status']) {
|
|
71
|
+
query.status = req.query['status'];
|
|
72
|
+
}
|
|
73
|
+
if (req.query['minConfidence']) {
|
|
74
|
+
query.minConfidence = parseFloat(req.query['minConfidence']);
|
|
75
|
+
}
|
|
76
|
+
if (req.query['search']) {
|
|
77
|
+
query.search = req.query['search'];
|
|
78
|
+
}
|
|
79
|
+
const patterns = await reader.getPatterns(query);
|
|
80
|
+
res.json(patterns);
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
next(error);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
/**
|
|
87
|
+
* GET /api/patterns/:id - Get pattern with locations
|
|
88
|
+
* @requirements 8.2
|
|
89
|
+
*/
|
|
90
|
+
router.get('/patterns/:id', async (req, res, next) => {
|
|
91
|
+
try {
|
|
92
|
+
const { id } = req.params;
|
|
93
|
+
if (!id) {
|
|
94
|
+
throw new BadRequestError('Pattern ID is required');
|
|
95
|
+
}
|
|
96
|
+
const pattern = await reader.getPattern(id);
|
|
97
|
+
if (!pattern) {
|
|
98
|
+
throw new NotFoundError(`Pattern not found: ${id}`);
|
|
99
|
+
}
|
|
100
|
+
res.json(pattern);
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
next(error);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
/**
|
|
107
|
+
* POST /api/patterns/:id/approve - Approve a pattern
|
|
108
|
+
* @requirements 8.3
|
|
109
|
+
*/
|
|
110
|
+
router.post('/patterns/:id/approve', async (req, res, next) => {
|
|
111
|
+
try {
|
|
112
|
+
const { id } = req.params;
|
|
113
|
+
if (!id) {
|
|
114
|
+
throw new BadRequestError('Pattern ID is required');
|
|
115
|
+
}
|
|
116
|
+
await reader.approvePattern(id);
|
|
117
|
+
res.json({ success: true, message: `Pattern ${id} approved` });
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
if (error instanceof Error && error.message.includes('not found')) {
|
|
121
|
+
next(new NotFoundError(error.message));
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
next(error);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
/**
|
|
129
|
+
* POST /api/patterns/:id/ignore - Ignore a pattern
|
|
130
|
+
* @requirements 8.4
|
|
131
|
+
*/
|
|
132
|
+
router.post('/patterns/:id/ignore', async (req, res, next) => {
|
|
133
|
+
try {
|
|
134
|
+
const { id } = req.params;
|
|
135
|
+
if (!id) {
|
|
136
|
+
throw new BadRequestError('Pattern ID is required');
|
|
137
|
+
}
|
|
138
|
+
await reader.ignorePattern(id);
|
|
139
|
+
res.json({ success: true, message: `Pattern ${id} ignored` });
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
if (error instanceof Error && error.message.includes('not found')) {
|
|
143
|
+
next(new NotFoundError(error.message));
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
next(error);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
/**
|
|
151
|
+
* POST /api/patterns/bulk-approve - Bulk approve multiple patterns
|
|
152
|
+
* Accepts an array of pattern IDs to approve at once
|
|
153
|
+
*/
|
|
154
|
+
router.post('/patterns/bulk-approve', async (req, res, next) => {
|
|
155
|
+
try {
|
|
156
|
+
const { ids } = req.body;
|
|
157
|
+
if (!ids || !Array.isArray(ids) || ids.length === 0) {
|
|
158
|
+
throw new BadRequestError('Array of pattern IDs is required');
|
|
159
|
+
}
|
|
160
|
+
const results = [];
|
|
161
|
+
for (const id of ids) {
|
|
162
|
+
try {
|
|
163
|
+
await reader.approvePattern(id);
|
|
164
|
+
results.push({ id, success: true });
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
results.push({
|
|
168
|
+
id,
|
|
169
|
+
success: false,
|
|
170
|
+
error: error instanceof Error ? error.message : 'Unknown error'
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const successCount = results.filter(r => r.success).length;
|
|
175
|
+
res.json({
|
|
176
|
+
success: true,
|
|
177
|
+
message: `Approved ${successCount} of ${ids.length} patterns`,
|
|
178
|
+
results
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
next(error);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
/**
|
|
186
|
+
* DELETE /api/patterns/:id - Delete a pattern
|
|
187
|
+
* @requirements 8.5
|
|
188
|
+
*/
|
|
189
|
+
router.delete('/patterns/:id', async (req, res, next) => {
|
|
190
|
+
try {
|
|
191
|
+
const { id } = req.params;
|
|
192
|
+
if (!id) {
|
|
193
|
+
throw new BadRequestError('Pattern ID is required');
|
|
194
|
+
}
|
|
195
|
+
await reader.deletePattern(id);
|
|
196
|
+
res.json({ success: true, message: `Pattern ${id} deleted` });
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
if (error instanceof Error && error.message.includes('not found')) {
|
|
200
|
+
next(new NotFoundError(error.message));
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
next(error);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
// ==========================================================================
|
|
208
|
+
// Violation Routes
|
|
209
|
+
// ==========================================================================
|
|
210
|
+
/**
|
|
211
|
+
* GET /api/violations - List all violations with optional filters
|
|
212
|
+
* @requirements 8.6
|
|
213
|
+
*/
|
|
214
|
+
router.get('/violations', async (req, res, next) => {
|
|
215
|
+
try {
|
|
216
|
+
const query = {};
|
|
217
|
+
if (req.query['severity']) {
|
|
218
|
+
query.severity = req.query['severity'];
|
|
219
|
+
}
|
|
220
|
+
if (req.query['file']) {
|
|
221
|
+
query.file = req.query['file'];
|
|
222
|
+
}
|
|
223
|
+
if (req.query['patternId']) {
|
|
224
|
+
query.patternId = req.query['patternId'];
|
|
225
|
+
}
|
|
226
|
+
if (req.query['search']) {
|
|
227
|
+
query.search = req.query['search'];
|
|
228
|
+
}
|
|
229
|
+
const violations = await reader.getViolations(query);
|
|
230
|
+
res.json(violations);
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
next(error);
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
/**
|
|
237
|
+
* GET /api/snippet - Get code snippet for a file location
|
|
238
|
+
*/
|
|
239
|
+
router.get('/snippet', async (req, res, next) => {
|
|
240
|
+
try {
|
|
241
|
+
const file = req.query['file'];
|
|
242
|
+
const line = parseInt(req.query['line'], 10);
|
|
243
|
+
const context = parseInt(req.query['context'], 10) || 3;
|
|
244
|
+
if (!file) {
|
|
245
|
+
throw new BadRequestError('File path is required');
|
|
246
|
+
}
|
|
247
|
+
if (isNaN(line)) {
|
|
248
|
+
throw new BadRequestError('Line number is required');
|
|
249
|
+
}
|
|
250
|
+
const snippet = await reader.getCodeSnippet(file, line, context);
|
|
251
|
+
if (!snippet) {
|
|
252
|
+
throw new NotFoundError(`Could not read file: ${file}`);
|
|
253
|
+
}
|
|
254
|
+
res.json(snippet);
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
next(error);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
// ==========================================================================
|
|
261
|
+
// File Routes
|
|
262
|
+
// ==========================================================================
|
|
263
|
+
/**
|
|
264
|
+
* GET /api/files - Get file tree
|
|
265
|
+
* @requirements 8.7
|
|
266
|
+
*/
|
|
267
|
+
router.get('/files', async (_req, res, next) => {
|
|
268
|
+
try {
|
|
269
|
+
const fileTree = await reader.getFileTree();
|
|
270
|
+
res.json(fileTree);
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
next(error);
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
/**
|
|
277
|
+
* GET /api/files/* - Get file details (wildcard path)
|
|
278
|
+
* @requirements 8.8
|
|
279
|
+
*/
|
|
280
|
+
router.get('/files/*', async (req, res, next) => {
|
|
281
|
+
try {
|
|
282
|
+
// Extract the file path from the wildcard
|
|
283
|
+
const filePath = req.params[0];
|
|
284
|
+
if (!filePath) {
|
|
285
|
+
throw new BadRequestError('File path is required');
|
|
286
|
+
}
|
|
287
|
+
const fileDetails = await reader.getFileDetails(filePath);
|
|
288
|
+
if (!fileDetails) {
|
|
289
|
+
throw new NotFoundError(`File not found or has no patterns/violations: ${filePath}`);
|
|
290
|
+
}
|
|
291
|
+
res.json(fileDetails);
|
|
292
|
+
}
|
|
293
|
+
catch (error) {
|
|
294
|
+
next(error);
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
// ==========================================================================
|
|
298
|
+
// Stats Routes
|
|
299
|
+
// ==========================================================================
|
|
300
|
+
/**
|
|
301
|
+
* GET /api/stats - Get dashboard statistics
|
|
302
|
+
* @requirements 8.9
|
|
303
|
+
*/
|
|
304
|
+
router.get('/stats', async (_req, res, next) => {
|
|
305
|
+
try {
|
|
306
|
+
const stats = await reader.getStats();
|
|
307
|
+
res.json(stats);
|
|
308
|
+
}
|
|
309
|
+
catch (error) {
|
|
310
|
+
next(error);
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
// ==========================================================================
|
|
314
|
+
// Config Routes
|
|
315
|
+
// ==========================================================================
|
|
316
|
+
/**
|
|
317
|
+
* GET /api/config - Get configuration
|
|
318
|
+
* @requirements 8.10
|
|
319
|
+
*/
|
|
320
|
+
router.get('/config', async (_req, res, next) => {
|
|
321
|
+
try {
|
|
322
|
+
const config = await reader.getConfig();
|
|
323
|
+
res.json(config);
|
|
324
|
+
}
|
|
325
|
+
catch (error) {
|
|
326
|
+
next(error);
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
/**
|
|
330
|
+
* PUT /api/config - Update configuration
|
|
331
|
+
* @requirements 8.11
|
|
332
|
+
*/
|
|
333
|
+
router.put('/config', async (req, res, next) => {
|
|
334
|
+
try {
|
|
335
|
+
const partialConfig = req.body;
|
|
336
|
+
if (!partialConfig || typeof partialConfig !== 'object') {
|
|
337
|
+
throw new BadRequestError('Invalid configuration format');
|
|
338
|
+
}
|
|
339
|
+
await reader.updateConfig(partialConfig);
|
|
340
|
+
const updatedConfig = await reader.getConfig();
|
|
341
|
+
res.json(updatedConfig);
|
|
342
|
+
}
|
|
343
|
+
catch (error) {
|
|
344
|
+
next(error);
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
// ==========================================================================
|
|
348
|
+
// Contract Routes (BE↔FE mismatch detection)
|
|
349
|
+
// ==========================================================================
|
|
350
|
+
/**
|
|
351
|
+
* GET /api/contracts - List all contracts with optional filters
|
|
352
|
+
*/
|
|
353
|
+
router.get('/contracts', async (req, res, next) => {
|
|
354
|
+
try {
|
|
355
|
+
const query = {};
|
|
356
|
+
if (req.query['status']) {
|
|
357
|
+
query.status = req.query['status'];
|
|
358
|
+
}
|
|
359
|
+
if (req.query['method']) {
|
|
360
|
+
query.method = req.query['method'];
|
|
361
|
+
}
|
|
362
|
+
if (req.query['hasMismatches'] !== undefined) {
|
|
363
|
+
query.hasMismatches = req.query['hasMismatches'] === 'true';
|
|
364
|
+
}
|
|
365
|
+
if (req.query['search']) {
|
|
366
|
+
query.search = req.query['search'];
|
|
367
|
+
}
|
|
368
|
+
const contracts = await reader.getContracts(query);
|
|
369
|
+
res.json(contracts);
|
|
370
|
+
}
|
|
371
|
+
catch (error) {
|
|
372
|
+
next(error);
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
/**
|
|
376
|
+
* GET /api/contracts/stats - Get contract statistics
|
|
377
|
+
*/
|
|
378
|
+
router.get('/contracts/stats', async (_req, res, next) => {
|
|
379
|
+
try {
|
|
380
|
+
const stats = await reader.getContractStats();
|
|
381
|
+
res.json(stats);
|
|
382
|
+
}
|
|
383
|
+
catch (error) {
|
|
384
|
+
next(error);
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
/**
|
|
388
|
+
* GET /api/contracts/:id - Get contract details
|
|
389
|
+
*/
|
|
390
|
+
router.get('/contracts/:id', async (req, res, next) => {
|
|
391
|
+
try {
|
|
392
|
+
const { id } = req.params;
|
|
393
|
+
if (!id) {
|
|
394
|
+
throw new BadRequestError('Contract ID is required');
|
|
395
|
+
}
|
|
396
|
+
const contract = await reader.getContract(id);
|
|
397
|
+
if (!contract) {
|
|
398
|
+
throw new NotFoundError(`Contract not found: ${id}`);
|
|
399
|
+
}
|
|
400
|
+
res.json(contract);
|
|
401
|
+
}
|
|
402
|
+
catch (error) {
|
|
403
|
+
next(error);
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
/**
|
|
407
|
+
* POST /api/contracts/:id/verify - Verify a contract
|
|
408
|
+
*/
|
|
409
|
+
router.post('/contracts/:id/verify', async (req, res, next) => {
|
|
410
|
+
try {
|
|
411
|
+
const { id } = req.params;
|
|
412
|
+
if (!id) {
|
|
413
|
+
throw new BadRequestError('Contract ID is required');
|
|
414
|
+
}
|
|
415
|
+
await reader.verifyContract(id);
|
|
416
|
+
res.json({ success: true, message: `Contract ${id} verified` });
|
|
417
|
+
}
|
|
418
|
+
catch (error) {
|
|
419
|
+
if (error instanceof Error && error.message.includes('not found')) {
|
|
420
|
+
next(new NotFoundError(error.message));
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
next(error);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
/**
|
|
428
|
+
* POST /api/contracts/:id/ignore - Ignore a contract
|
|
429
|
+
*/
|
|
430
|
+
router.post('/contracts/:id/ignore', async (req, res, next) => {
|
|
431
|
+
try {
|
|
432
|
+
const { id } = req.params;
|
|
433
|
+
if (!id) {
|
|
434
|
+
throw new BadRequestError('Contract ID is required');
|
|
435
|
+
}
|
|
436
|
+
await reader.ignoreContract(id);
|
|
437
|
+
res.json({ success: true, message: `Contract ${id} ignored` });
|
|
438
|
+
}
|
|
439
|
+
catch (error) {
|
|
440
|
+
if (error instanceof Error && error.message.includes('not found')) {
|
|
441
|
+
next(new NotFoundError(error.message));
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
next(error);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
// ==========================================================================
|
|
449
|
+
// Trend / History Routes
|
|
450
|
+
// ==========================================================================
|
|
451
|
+
/**
|
|
452
|
+
* GET /api/trends - Get pattern trend summary
|
|
453
|
+
* Query params: period (7d, 30d, 90d)
|
|
454
|
+
*/
|
|
455
|
+
router.get('/trends', async (req, res, next) => {
|
|
456
|
+
try {
|
|
457
|
+
const period = req.query['period'] || '7d';
|
|
458
|
+
if (!['7d', '30d', '90d'].includes(period)) {
|
|
459
|
+
throw new BadRequestError('Invalid period. Use 7d, 30d, or 90d');
|
|
460
|
+
}
|
|
461
|
+
const trends = await reader.getTrends(period);
|
|
462
|
+
if (!trends) {
|
|
463
|
+
res.json({
|
|
464
|
+
message: 'Not enough history data. Run more scans to see trends.',
|
|
465
|
+
period,
|
|
466
|
+
regressions: [],
|
|
467
|
+
improvements: [],
|
|
468
|
+
stable: 0,
|
|
469
|
+
overallTrend: 'stable',
|
|
470
|
+
healthDelta: 0,
|
|
471
|
+
categoryTrends: {},
|
|
472
|
+
});
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
res.json(trends);
|
|
476
|
+
}
|
|
477
|
+
catch (error) {
|
|
478
|
+
next(error);
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
/**
|
|
482
|
+
* GET /api/trends/snapshots - Get historical snapshots for charting
|
|
483
|
+
* Query params: limit (default 30)
|
|
484
|
+
*/
|
|
485
|
+
router.get('/trends/snapshots', async (req, res, next) => {
|
|
486
|
+
try {
|
|
487
|
+
const limit = parseInt(req.query['limit'], 10) || 30;
|
|
488
|
+
const snapshots = await reader.getSnapshots(limit);
|
|
489
|
+
res.json(snapshots);
|
|
490
|
+
}
|
|
491
|
+
catch (error) {
|
|
492
|
+
next(error);
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
// ==========================================================================
|
|
496
|
+
// Galaxy Routes (3D Database Visualization)
|
|
497
|
+
// ==========================================================================
|
|
498
|
+
/**
|
|
499
|
+
* GET /api/galaxy - Get Galaxy visualization data
|
|
500
|
+
*
|
|
501
|
+
* Transforms boundary scanner and call graph data into the format
|
|
502
|
+
* required by the 3D Galaxy visualization.
|
|
503
|
+
*/
|
|
504
|
+
router.get('/galaxy', async (_req, res, next) => {
|
|
505
|
+
try {
|
|
506
|
+
const transformer = createGalaxyDataTransformer(reader.directory);
|
|
507
|
+
const galaxyData = await transformer.transform();
|
|
508
|
+
res.json(galaxyData);
|
|
509
|
+
}
|
|
510
|
+
catch (error) {
|
|
511
|
+
next(error);
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
// ==========================================================================
|
|
515
|
+
// Project Routes (Multi-project management)
|
|
516
|
+
// ==========================================================================
|
|
517
|
+
/**
|
|
518
|
+
* GET /api/projects - List all registered projects
|
|
519
|
+
*/
|
|
520
|
+
router.get('/projects', async (_req, res, next) => {
|
|
521
|
+
try {
|
|
522
|
+
const registry = await getProjectRegistry();
|
|
523
|
+
const projects = registry.getValid();
|
|
524
|
+
const activeProject = registry.getActive();
|
|
525
|
+
// Sort by last accessed
|
|
526
|
+
projects.sort((a, b) => new Date(b.lastAccessedAt).getTime() -
|
|
527
|
+
new Date(a.lastAccessedAt).getTime());
|
|
528
|
+
res.json({
|
|
529
|
+
totalProjects: registry.count,
|
|
530
|
+
activeProject: activeProject?.name ?? null,
|
|
531
|
+
projects: projects.map((p) => ({
|
|
532
|
+
id: p.id,
|
|
533
|
+
name: p.name,
|
|
534
|
+
path: p.path,
|
|
535
|
+
language: p.language,
|
|
536
|
+
framework: p.framework,
|
|
537
|
+
health: p.health,
|
|
538
|
+
healthScore: p.healthScore,
|
|
539
|
+
lastAccessedAt: p.lastAccessedAt,
|
|
540
|
+
isActive: p.id === activeProject?.id,
|
|
541
|
+
isValid: p.isValid !== false,
|
|
542
|
+
})),
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
catch (error) {
|
|
546
|
+
next(error);
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
/**
|
|
550
|
+
* POST /api/projects/switch - Switch active project
|
|
551
|
+
*/
|
|
552
|
+
router.post('/projects/switch', async (req, res, next) => {
|
|
553
|
+
try {
|
|
554
|
+
const { projectId } = req.body;
|
|
555
|
+
if (!projectId) {
|
|
556
|
+
throw new BadRequestError('Project ID is required');
|
|
557
|
+
}
|
|
558
|
+
const registry = await getProjectRegistry();
|
|
559
|
+
const project = registry.get(projectId);
|
|
560
|
+
if (!project) {
|
|
561
|
+
throw new NotFoundError(`Project not found: ${projectId}`);
|
|
562
|
+
}
|
|
563
|
+
await registry.setActive(projectId);
|
|
564
|
+
res.json({
|
|
565
|
+
success: true,
|
|
566
|
+
message: `Switched to ${project.name}`,
|
|
567
|
+
project: {
|
|
568
|
+
id: project.id,
|
|
569
|
+
name: project.name,
|
|
570
|
+
path: project.path,
|
|
571
|
+
},
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
catch (error) {
|
|
575
|
+
next(error);
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
/**
|
|
579
|
+
* GET /api/projects/:id - Get project details
|
|
580
|
+
*/
|
|
581
|
+
router.get('/projects/:id', async (req, res, next) => {
|
|
582
|
+
try {
|
|
583
|
+
const { id } = req.params;
|
|
584
|
+
if (!id) {
|
|
585
|
+
throw new BadRequestError('Project ID is required');
|
|
586
|
+
}
|
|
587
|
+
const registry = await getProjectRegistry();
|
|
588
|
+
const project = registry.get(id);
|
|
589
|
+
if (!project) {
|
|
590
|
+
throw new NotFoundError(`Project not found: ${id}`);
|
|
591
|
+
}
|
|
592
|
+
const activeProject = registry.getActive();
|
|
593
|
+
res.json({
|
|
594
|
+
...project,
|
|
595
|
+
isActive: project.id === activeProject?.id,
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
catch (error) {
|
|
599
|
+
next(error);
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
// ==========================================================================
|
|
603
|
+
// Quality Gates Routes
|
|
604
|
+
// ==========================================================================
|
|
605
|
+
/**
|
|
606
|
+
* GET /api/quality-gates - Quality gates API
|
|
607
|
+
* Actions: latest, history, policies, policy
|
|
608
|
+
*/
|
|
609
|
+
router.get('/quality-gates', (req, res) => {
|
|
610
|
+
handleQualityGatesRequest(req, res, reader.directory);
|
|
611
|
+
});
|
|
612
|
+
/**
|
|
613
|
+
* POST /api/quality-gates - Quality gates API (for run action)
|
|
614
|
+
* Actions: run
|
|
615
|
+
*/
|
|
616
|
+
router.post('/quality-gates', (req, res) => {
|
|
617
|
+
handleQualityGatesRequest(req, res, reader.directory);
|
|
618
|
+
});
|
|
619
|
+
return router;
|
|
620
|
+
}
|
|
621
|
+
// ============================================================================
|
|
622
|
+
// Error Handling Middleware
|
|
623
|
+
// ============================================================================
|
|
624
|
+
/**
|
|
625
|
+
* Error handling middleware for API routes
|
|
626
|
+
* @requirements 8.12 - Return appropriate HTTP status codes and error messages
|
|
627
|
+
*/
|
|
628
|
+
export function errorHandler(err, _req, res, _next) {
|
|
629
|
+
// Determine status code
|
|
630
|
+
const statusCode = 'statusCode' in err ? err.statusCode : 500;
|
|
631
|
+
// Log server errors
|
|
632
|
+
if (statusCode >= 500) {
|
|
633
|
+
console.error('Server error:', err);
|
|
634
|
+
}
|
|
635
|
+
// Send JSON error response
|
|
636
|
+
res.status(statusCode).json({
|
|
637
|
+
error: err.name || 'Error',
|
|
638
|
+
message: err.message || 'An unexpected error occurred',
|
|
639
|
+
statusCode,
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* 404 handler for unknown routes
|
|
644
|
+
*/
|
|
645
|
+
export function notFoundHandler(req, res) {
|
|
646
|
+
res.status(404).json({
|
|
647
|
+
error: 'NotFound',
|
|
648
|
+
message: `Route not found: ${req.method} ${req.path}`,
|
|
649
|
+
statusCode: 404,
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
//# sourceMappingURL=api-routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-routes.js","sourceRoot":"","sources":["../../src/server/api-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,MAAM,EAAkD,MAAM,SAAS,CAAC;AACjF,OAAO,EAAE,eAAe,EAA4D,MAAM,wBAAwB,CAAC;AACnH,OAAO,EAAE,2BAA2B,EAAE,MAAM,8BAA8B,CAAC;AAC3E,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AAUnE,+EAA+E;AAC/E,gBAAgB;AAChB,+EAA+E;AAE/E,MAAM,OAAO,aAAc,SAAQ,KAAK;IACtC,UAAU,GAAG,GAAG,CAAC;IACjB,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;IAC9B,CAAC;CACF;AAED,MAAM,OAAO,eAAgB,SAAQ,KAAK;IACxC,UAAU,GAAG,GAAG,CAAC;IACjB,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;IAChC,CAAC;CACF;AAED,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IAC5C,UAAU,GAAG,GAAG,CAAC;IACjB,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;IACpC,CAAC;CACF;AAED,+EAA+E;AAC/E,qBAAqB;AACrB,+EAA+E;AAE/E;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,MAAuB;IACrD,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC;IAExB,6EAA6E;IAC7E,iBAAiB;IACjB,6EAA6E;IAE7E;;;OAGG;IACH,MAAM,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QAChF,IAAI,CAAC;YACH,MAAM,KAAK,GAAiB,EAAE,CAAC;YAE/B,IAAI,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC1B,KAAK,CAAC,QAAQ,GAAG,GAAG,CAAC,KAAK,CAAC,UAAU,CAAW,CAAC;YACnD,CAAC;YACD,IAAI,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACxB,KAAK,CAAC,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAW,CAAC;YAC/C,CAAC;YACD,IAAI,GAAG,CAAC,KAAK,CAAC,eAAe,CAAC,EAAE,CAAC;gBAC/B,KAAK,CAAC,aAAa,GAAG,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,eAAe,CAAW,CAAC,CAAC;YACzE,CAAC;YACD,IAAI,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACxB,KAAK,CAAC,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAW,CAAC;YAC/C,CAAC;YAED,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;YACjD,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACrB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,KAAK,CAAC,CAAC;QACd,CAAC;IACH,CAAC,CAAC,CAAC;IAEH;;;OAGG;IACH,MAAM,CAAC,GAAG,CAAC,eAAe,EAAE,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QACpF,IAAI,CAAC;YACH,MAAM,EAAE,EAAE,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC;YAE1B,IAAI,CAAC,EAAE,EAAE,CAAC;gBACR,MAAM,IAAI,eAAe,CAAC,wBAAwB,CAAC,CAAC;YACtD,CAAC;YAED,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;YAE5C,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,MAAM,IAAI,aAAa,CAAC,sBAAsB,EAAE,EAAE,CAAC,CAAC;YACtD,CAAC;YAED,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACpB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,KAAK,CAAC,CAAC;QACd,CAAC;IACH,CAAC,CAAC,CAAC;IAEH;;;OAGG;IACH,MAAM,CAAC,IAAI,CAAC,uBAAuB,EAAE,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QAC7F,IAAI,CAAC;YACH,MAAM,EAAE,EAAE,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC;YAE1B,IAAI,CAAC,EAAE,EAAE,CAAC;gBACR,MAAM,IAAI,eAAe,CAAC,wBAAwB,CAAC,CAAC;YACtD,CAAC;YAED,MAAM,MAAM,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC;YAChC,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,CAAC,CAAC;QACjE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;gBAClE,IAAI,CAAC,IAAI,aAAa,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;YACzC,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,KAAK,CAAC,CAAC;YACd,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAC;IAEH;;;OAGG;IACH,MAAM,CAAC,IAAI,CAAC,sBAAsB,EAAE,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QAC5F,IAAI,CAAC;YACH,MAAM,EAAE,EAAE,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC;YAE1B,IAAI,CAAC,EAAE,EAAE,CAAC;gBACR,MAAM,IAAI,eAAe,CAAC,wBAAwB,CAAC,CAAC;YACtD,CAAC;YAED,MAAM,MAAM,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC;YAC/B,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC,CAAC;QAChE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;gBAClE,IAAI,CAAC,IAAI,aAAa,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;YACzC,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,KAAK,CAAC,CAAC;YACd,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAC;IAEH;;;OAGG;IACH,MAAM,CAAC,IAAI,CAAC,wBAAwB,EAAE,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QAC9F,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,EAAE,GAAG,GAAG,CAAC,IAAyB,CAAC;YAE9C,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACpD,MAAM,IAAI,eAAe,CAAC,kCAAkC,CAAC,CAAC;YAChE,CAAC;YAED,MAAM,OAAO,GAAuD,EAAE,CAAC;YAEvE,KAAK,MAAM,EAAE,IAAI,GAAG,EAAE,CAAC;gBACrB,IAAI,CAAC;oBACH,MAAM,MAAM,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC;oBAChC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;gBACtC,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,OAAO,CAAC,IAAI,CAAC;wBACX,EAAE;wBACF,OAAO,EAAE,KAAK;wBACd,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe;qBAChE,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;YAED,MAAM,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC;YAC3D,GAAG,CAAC,IAAI,CAAC;gBACP,OAAO,EAAE,IAAI;gBACb,OAAO,EAAE,YAAY,YAAY,OAAO,GAAG,CAAC,MAAM,WAAW;gBAC7D,OAAO;aACR,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,KAAK,CAAC,CAAC;QACd,CAAC;IACH,CAAC,CAAC,CAAC;IAEH;;;OAGG;IACH,MAAM,CAAC,MAAM,CAAC,eAAe,EAAE,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QACvF,IAAI,CAAC;YACH,MAAM,EAAE,EAAE,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC;YAE1B,IAAI,CAAC,EAAE,EAAE,CAAC;gBACR,MAAM,IAAI,eAAe,CAAC,wBAAwB,CAAC,CAAC;YACtD,CAAC;YAED,MAAM,MAAM,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC;YAC/B,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC,CAAC;QAChE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;gBAClE,IAAI,CAAC,IAAI,aAAa,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;YACzC,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,KAAK,CAAC,CAAC;YACd,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,6EAA6E;IAC7E,mBAAmB;IACnB,6EAA6E;IAE7E;;;OAGG;IACH,MAAM,CAAC,GAAG,CAAC,aAAa,EAAE,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QAClF,IAAI,CAAC;YACH,MAAM,KAAK,GAAmB,EAAE,CAAC;YAEjC,IAAI,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC1B,KAAK,CAAC,QAAQ,GAAG,GAAG,CAAC,KAAK,CAAC,UAAU,CAAW,CAAC;YACnD,CAAC;YACD,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;gBACtB,KAAK,CAAC,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,CAAW,CAAC;YAC3C,CAAC;YACD,IAAI,GAAG,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE,CAAC;gBAC3B,KAAK,CAAC,SAAS,GAAG,GAAG,CAAC,KAAK,CAAC,WAAW,CAAW,CAAC;YACrD,CAAC;YACD,IAAI,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACxB,KAAK,CAAC,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAW,CAAC;YAC/C,CAAC;YAED,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;YACrD,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACvB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,KAAK,CAAC,CAAC;QACd,CAAC;IACH,CAAC,CAAC,CAAC;IAEH;;OAEG;IACH,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QAC/E,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,CAAW,CAAC;YACzC,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAW,EAAE,EAAE,CAAC,CAAC;YACvD,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAW,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC;YAElE,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,MAAM,IAAI,eAAe,CAAC,uBAAuB,CAAC,CAAC;YACrD,CAAC;YACD,IAAI,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;gBAChB,MAAM,IAAI,eAAe,CAAC,yBAAyB,CAAC,CAAC;YACvD,CAAC;YAED,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;YAEjE,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,MAAM,IAAI,aAAa,CAAC,wBAAwB,IAAI,EAAE,CAAC,CAAC;YAC1D,CAAC;YAED,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACpB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,KAAK,CAAC,CAAC;QACd,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,6EAA6E;IAC7E,cAAc;IACd,6EAA6E;IAE7E;;;OAGG;IACH,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,EAAE,IAAa,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QAC9E,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,WAAW,EAAE,CAAC;YAC5C,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACrB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,KAAK,CAAC,CAAC;QACd,CAAC;IACH,CAAC,CAAC,CAAC;IAEH;;;OAGG;IACH,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QAC/E,IAAI,CAAC;YACH,0CAA0C;YAC1C,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;YAE/B,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,MAAM,IAAI,eAAe,CAAC,uBAAuB,CAAC,CAAC;YACrD,CAAC;YAED,MAAM,WAAW,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;YAE1D,IAAI,CAAC,WAAW,EAAE,CAAC;gBACjB,MAAM,IAAI,aAAa,CAAC,iDAAiD,QAAQ,EAAE,CAAC,CAAC;YACvF,CAAC;YAED,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACxB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,KAAK,CAAC,CAAC;QACd,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,6EAA6E;IAC7E,eAAe;IACf,6EAA6E;IAE7E;;;OAGG;IACH,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,EAAE,IAAa,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QAC9E,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,QAAQ,EAAE,CAAC;YACtC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAClB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,KAAK,CAAC,CAAC;QACd,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,6EAA6E;IAC7E,gBAAgB;IAChB,6EAA6E;IAE7E;;;OAGG;IACH,MAAM,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,EAAE,IAAa,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QAC/E,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,SAAS,EAAE,CAAC;YACxC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACnB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,KAAK,CAAC,CAAC;QACd,CAAC;IACH,CAAC,CAAC,CAAC;IAEH;;;OAGG;IACH,MAAM,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QAC9E,IAAI,CAAC;YACH,MAAM,aAAa,GAAG,GAAG,CAAC,IAA4B,CAAC;YAEvD,IAAI,CAAC,aAAa,IAAI,OAAO,aAAa,KAAK,QAAQ,EAAE,CAAC;gBACxD,MAAM,IAAI,eAAe,CAAC,8BAA8B,CAAC,CAAC;YAC5D,CAAC;YAED,MAAM,MAAM,CAAC,YAAY,CAAC,aAAa,CAAC,CAAC;YACzC,MAAM,aAAa,GAAG,MAAM,MAAM,CAAC,SAAS,EAAE,CAAC;YAC/C,GAAG,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC1B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,KAAK,CAAC,CAAC;QACd,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,6EAA6E;IAC7E,6CAA6C;IAC7C,6EAA6E;IAE7E;;OAEG;IACH,MAAM,CAAC,GAAG,CAAC,YAAY,EAAE,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QACjF,IAAI,CAAC;YACH,MAAM,KAAK,GAAmF,EAAE,CAAC;YAEjG,IAAI,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACxB,KAAK,CAAC,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAW,CAAC;YAC/C,CAAC;YACD,IAAI,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACxB,KAAK,CAAC,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAW,CAAC;YAC/C,CAAC;YACD,IAAI,GAAG,CAAC,KAAK,CAAC,eAAe,CAAC,KAAK,SAAS,EAAE,CAAC;gBAC7C,KAAK,CAAC,aAAa,GAAG,GAAG,CAAC,KAAK,CAAC,eAAe,CAAC,KAAK,MAAM,CAAC;YAC9D,CAAC;YACD,IAAI,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACxB,KAAK,CAAC,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAW,CAAC;YAC/C,CAAC;YAED,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;YACnD,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACtB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,KAAK,CAAC,CAAC;QACd,CAAC;IACH,CAAC,CAAC,CAAC;IAEH;;OAEG;IACH,MAAM,CAAC,GAAG,CAAC,kBAAkB,EAAE,KAAK,EAAE,IAAa,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QACxF,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,gBAAgB,EAAE,CAAC;YAC9C,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAClB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,KAAK,CAAC,CAAC;QACd,CAAC;IACH,CAAC,CAAC,CAAC;IAEH;;OAEG;IACH,MAAM,CAAC,GAAG,CAAC,gBAAgB,EAAE,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QACrF,IAAI,CAAC;YACH,MAAM,EAAE,EAAE,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC;YAE1B,IAAI,CAAC,EAAE,EAAE,CAAC;gBACR,MAAM,IAAI,eAAe,CAAC,yBAAyB,CAAC,CAAC;YACvD,CAAC;YAED,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;YAE9C,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,MAAM,IAAI,aAAa,CAAC,uBAAuB,EAAE,EAAE,CAAC,CAAC;YACvD,CAAC;YAED,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACrB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,KAAK,CAAC,CAAC;QACd,CAAC;IACH,CAAC,CAAC,CAAC;IAEH;;OAEG;IACH,MAAM,CAAC,IAAI,CAAC,uBAAuB,EAAE,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QAC7F,IAAI,CAAC;YACH,MAAM,EAAE,EAAE,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC;YAE1B,IAAI,CAAC,EAAE,EAAE,CAAC;gBACR,MAAM,IAAI,eAAe,CAAC,yBAAyB,CAAC,CAAC;YACvD,CAAC;YAED,MAAM,MAAM,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC;YAChC,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,CAAC,CAAC;QAClE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;gBAClE,IAAI,CAAC,IAAI,aAAa,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;YACzC,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,KAAK,CAAC,CAAC;YACd,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAC;IAEH;;OAEG;IACH,MAAM,CAAC,IAAI,CAAC,uBAAuB,EAAE,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QAC7F,IAAI,CAAC;YACH,MAAM,EAAE,EAAE,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC;YAE1B,IAAI,CAAC,EAAE,EAAE,CAAC;gBACR,MAAM,IAAI,eAAe,CAAC,yBAAyB,CAAC,CAAC;YACvD,CAAC;YAED,MAAM,MAAM,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC;YAChC,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,CAAC,CAAC;QACjE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;gBAClE,IAAI,CAAC,IAAI,aAAa,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;YACzC,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,KAAK,CAAC,CAAC;YACd,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,6EAA6E;IAC7E,yBAAyB;IACzB,6EAA6E;IAE7E;;;OAGG;IACH,MAAM,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QAC9E,IAAI,CAAC;YACH,MAAM,MAAM,GAAI,GAAG,CAAC,KAAK,CAAC,QAAQ,CAA0B,IAAI,IAAI,CAAC;YAErE,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC3C,MAAM,IAAI,eAAe,CAAC,qCAAqC,CAAC,CAAC;YACnE,CAAC;YAED,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;YAE9C,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,GAAG,CAAC,IAAI,CAAC;oBACP,OAAO,EAAE,wDAAwD;oBACjE,MAAM;oBACN,WAAW,EAAE,EAAE;oBACf,YAAY,EAAE,EAAE;oBAChB,MAAM,EAAE,CAAC;oBACT,YAAY,EAAE,QAAQ;oBACtB,WAAW,EAAE,CAAC;oBACd,cAAc,EAAE,EAAE;iBACnB,CAAC,CAAC;gBACH,OAAO;YACT,CAAC;YAED,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACnB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,KAAK,CAAC,CAAC;QACd,CAAC;IACH,CAAC,CAAC,CAAC;IAEH;;;OAGG;IACH,MAAM,CAAC,GAAG,CAAC,mBAAmB,EAAE,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QACxF,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAW,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC;YAC/D,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;YACnD,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACtB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,KAAK,CAAC,CAAC;QACd,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,6EAA6E;IAC7E,4CAA4C;IAC5C,6EAA6E;IAE7E;;;;;OAKG;IACH,MAAM,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,EAAE,IAAa,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QAC/E,IAAI,CAAC;YACH,MAAM,WAAW,GAAG,2BAA2B,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;YAClE,MAAM,UAAU,GAAG,MAAM,WAAW,CAAC,SAAS,EAAE,CAAC;YACjD,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACvB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,KAAK,CAAC,CAAC;QACd,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,6EAA6E;IAC7E,4CAA4C;IAC5C,6EAA6E;IAE7E;;OAEG;IACH,MAAM,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,EAAE,IAAa,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QACjF,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,kBAAkB,EAAE,CAAC;YAC5C,MAAM,QAAQ,GAAG,QAAQ,CAAC,QAAQ,EAAE,CAAC;YACrC,MAAM,aAAa,GAAG,QAAQ,CAAC,SAAS,EAAE,CAAC;YAE3C,wBAAwB;YACxB,QAAQ,CAAC,IAAI,CACX,CAAC,CAAqB,EAAE,CAAqB,EAAE,EAAE,CAC/C,IAAI,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,OAAO,EAAE;gBACpC,IAAI,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,OAAO,EAAE,CACvC,CAAC;YAEF,GAAG,CAAC,IAAI,CAAC;gBACP,aAAa,EAAE,QAAQ,CAAC,KAAK;gBAC7B,aAAa,EAAE,aAAa,EAAE,IAAI,IAAI,IAAI;gBAC1C,QAAQ,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAqB,EAAE,EAAE,CAAC,CAAC;oBACjD,EAAE,EAAE,CAAC,CAAC,EAAE;oBACR,IAAI,EAAE,CAAC,CAAC,IAAI;oBACZ,IAAI,EAAE,CAAC,CAAC,IAAI;oBACZ,QAAQ,EAAE,CAAC,CAAC,QAAQ;oBACpB,SAAS,EAAE,CAAC,CAAC,SAAS;oBACtB,MAAM,EAAE,CAAC,CAAC,MAAM;oBAChB,WAAW,EAAE,CAAC,CAAC,WAAW;oBAC1B,cAAc,EAAE,CAAC,CAAC,cAAc;oBAChC,QAAQ,EAAE,CAAC,CAAC,EAAE,KAAK,aAAa,EAAE,EAAE;oBACpC,OAAO,EAAE,CAAC,CAAC,OAAO,KAAK,KAAK;iBAC7B,CAAC,CAAC;aACJ,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,KAAK,CAAC,CAAC;QACd,CAAC;IACH,CAAC,CAAC,CAAC;IAEH;;OAEG;IACH,MAAM,CAAC,IAAI,CAAC,kBAAkB,EAAE,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QACxF,IAAI,CAAC;YACH,MAAM,EAAE,SAAS,EAAE,GAAG,GAAG,CAAC,IAA6B,CAAC;YAExD,IAAI,CAAC,SAAS,EAAE,CAAC;gBACf,MAAM,IAAI,eAAe,CAAC,wBAAwB,CAAC,CAAC;YACtD,CAAC;YAED,MAAM,QAAQ,GAAG,MAAM,kBAAkB,EAAE,CAAC;YAC5C,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YAExC,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,MAAM,IAAI,aAAa,CAAC,sBAAsB,SAAS,EAAE,CAAC,CAAC;YAC7D,CAAC;YAED,MAAM,QAAQ,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;YAEpC,GAAG,CAAC,IAAI,CAAC;gBACP,OAAO,EAAE,IAAI;gBACb,OAAO,EAAE,eAAe,OAAO,CAAC,IAAI,EAAE;gBACtC,OAAO,EAAE;oBACP,EAAE,EAAE,OAAO,CAAC,EAAE;oBACd,IAAI,EAAE,OAAO,CAAC,IAAI;oBAClB,IAAI,EAAE,OAAO,CAAC,IAAI;iBACnB;aACF,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,KAAK,CAAC,CAAC;QACd,CAAC;IACH,CAAC,CAAC,CAAC;IAEH;;OAEG;IACH,MAAM,CAAC,GAAG,CAAC,eAAe,EAAE,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QACpF,IAAI,CAAC;YACH,MAAM,EAAE,EAAE,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC;YAE1B,IAAI,CAAC,EAAE,EAAE,CAAC;gBACR,MAAM,IAAI,eAAe,CAAC,wBAAwB,CAAC,CAAC;YACtD,CAAC;YAED,MAAM,QAAQ,GAAG,MAAM,kBAAkB,EAAE,CAAC;YAC5C,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAEjC,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,MAAM,IAAI,aAAa,CAAC,sBAAsB,EAAE,EAAE,CAAC,CAAC;YACtD,CAAC;YAED,MAAM,aAAa,GAAG,QAAQ,CAAC,SAAS,EAAE,CAAC;YAE3C,GAAG,CAAC,IAAI,CAAC;gBACP,GAAG,OAAO;gBACV,QAAQ,EAAE,OAAO,CAAC,EAAE,KAAK,aAAa,EAAE,EAAE;aAC3C,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,KAAK,CAAC,CAAC;QACd,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,6EAA6E;IAC7E,uBAAuB;IACvB,6EAA6E;IAE7E;;;OAGG;IACH,MAAM,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC,GAAY,EAAE,GAAa,EAAE,EAAE;QAC3D,yBAAyB,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH;;;OAGG;IACH,MAAM,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC,GAAY,EAAE,GAAa,EAAE,EAAE;QAC5D,yBAAyB,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,+EAA+E;AAC/E,4BAA4B;AAC5B,+EAA+E;AAE/E;;;GAGG;AACH,MAAM,UAAU,YAAY,CAC1B,GAAqB,EACrB,IAAa,EACb,GAAa,EACb,KAAmB;IAEnB,wBAAwB;IACxB,MAAM,UAAU,GAAG,YAAY,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC;IAE9D,oBAAoB;IACpB,IAAI,UAAU,IAAI,GAAG,EAAE,CAAC;QACtB,OAAO,CAAC,KAAK,CAAC,eAAe,EAAE,GAAG,CAAC,CAAC;IACtC,CAAC;IAED,2BAA2B;IAC3B,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC;QAC1B,KAAK,EAAE,GAAG,CAAC,IAAI,IAAI,OAAO;QAC1B,OAAO,EAAE,GAAG,CAAC,OAAO,IAAI,8BAA8B;QACtD,UAAU;KACX,CAAC,CAAC;AACL,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,GAAY,EAAE,GAAa;IACzD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;QACnB,KAAK,EAAE,UAAU;QACjB,OAAO,EAAE,oBAAoB,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,IAAI,EAAE;QACrD,UAAU,EAAE,GAAG;KAChB,CAAC,CAAC;AACL,CAAC"}
|