tailprint 0.0.0-stage → 1.0.0

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 (87) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +160 -2
  3. package/dist/config/env.d.ts +77 -0
  4. package/dist/config/env.js +38 -0
  5. package/dist/config/env.js.map +1 -0
  6. package/dist/config/plans.d.ts +15 -0
  7. package/dist/config/plans.js +104 -0
  8. package/dist/config/plans.js.map +1 -0
  9. package/dist/config/templates.d.ts +10 -0
  10. package/dist/config/templates.js +746 -0
  11. package/dist/config/templates.js.map +1 -0
  12. package/dist/mcp/server.d.ts +10 -0
  13. package/dist/mcp/server.js +897 -0
  14. package/dist/mcp/server.js.map +1 -0
  15. package/dist/mcp/stdio.d.ts +2 -0
  16. package/dist/mcp/stdio.js +27 -0
  17. package/dist/mcp/stdio.js.map +1 -0
  18. package/dist/routes/dashboard.d.ts +2 -0
  19. package/dist/routes/dashboard.js +454 -0
  20. package/dist/routes/dashboard.js.map +1 -0
  21. package/dist/routes/mcp.d.ts +2 -0
  22. package/dist/routes/mcp.js +329 -0
  23. package/dist/routes/mcp.js.map +1 -0
  24. package/dist/routes/render.d.ts +2 -0
  25. package/dist/routes/render.js +444 -0
  26. package/dist/routes/render.js.map +1 -0
  27. package/dist/routes/scrape.d.ts +2 -0
  28. package/dist/routes/scrape.js +132 -0
  29. package/dist/routes/scrape.js.map +1 -0
  30. package/dist/routes/templates.d.ts +2 -0
  31. package/dist/routes/templates.js +2449 -0
  32. package/dist/routes/templates.js.map +1 -0
  33. package/dist/routes/usage.d.ts +31 -0
  34. package/dist/routes/usage.js +73 -0
  35. package/dist/routes/usage.js.map +1 -0
  36. package/dist/routes/webhooks.d.ts +2 -0
  37. package/dist/routes/webhooks.js +227 -0
  38. package/dist/routes/webhooks.js.map +1 -0
  39. package/dist/schemas/render.d.ts +422 -0
  40. package/dist/schemas/render.js +91 -0
  41. package/dist/schemas/render.js.map +1 -0
  42. package/dist/schemas/scrape.d.ts +65 -0
  43. package/dist/schemas/scrape.js +24 -0
  44. package/dist/schemas/scrape.js.map +1 -0
  45. package/dist/server.d.ts +2 -0
  46. package/dist/server.js +206 -0
  47. package/dist/server.js.map +1 -0
  48. package/dist/services/browser.d.ts +21 -0
  49. package/dist/services/browser.js +299 -0
  50. package/dist/services/browser.js.map +1 -0
  51. package/dist/services/carousel.d.ts +43 -0
  52. package/dist/services/carousel.js +300 -0
  53. package/dist/services/carousel.js.map +1 -0
  54. package/dist/services/chart.d.ts +29 -0
  55. package/dist/services/chart.js +129 -0
  56. package/dist/services/chart.js.map +1 -0
  57. package/dist/services/eInvoice.d.ts +62 -0
  58. package/dist/services/eInvoice.js +413 -0
  59. package/dist/services/eInvoice.js.map +1 -0
  60. package/dist/services/email.d.ts +24 -0
  61. package/dist/services/email.js +177 -0
  62. package/dist/services/email.js.map +1 -0
  63. package/dist/services/mockup.d.ts +14 -0
  64. package/dist/services/mockup.js +130 -0
  65. package/dist/services/mockup.js.map +1 -0
  66. package/dist/services/pdfStamper.d.ts +16 -0
  67. package/dist/services/pdfStamper.js +47 -0
  68. package/dist/services/pdfStamper.js.map +1 -0
  69. package/dist/services/qr.d.ts +20 -0
  70. package/dist/services/qr.js +44 -0
  71. package/dist/services/qr.js.map +1 -0
  72. package/dist/services/redis.d.ts +65 -0
  73. package/dist/services/redis.js +318 -0
  74. package/dist/services/redis.js.map +1 -0
  75. package/dist/services/scraper.d.ts +16 -0
  76. package/dist/services/scraper.js +138 -0
  77. package/dist/services/scraper.js.map +1 -0
  78. package/dist/services/storage.d.ts +38 -0
  79. package/dist/services/storage.js +123 -0
  80. package/dist/services/storage.js.map +1 -0
  81. package/dist/services/stripe.d.ts +32 -0
  82. package/dist/services/stripe.js +229 -0
  83. package/dist/services/stripe.js.map +1 -0
  84. package/dist/services/visualDiff.d.ts +33 -0
  85. package/dist/services/visualDiff.js +218 -0
  86. package/dist/services/visualDiff.js.map +1 -0
  87. package/package.json +74 -4
@@ -0,0 +1,444 @@
1
+ import { z } from 'zod';
2
+ import { env } from '../config/env.js';
3
+ import { RenderPdfRequestSchema, RenderImageRequestSchema, RenderEInvoiceRequestSchema, HostedFileResponseSchema, ErrorResponseSchema, } from '../schemas/render.js';
4
+ import { browserService } from '../services/browser.js';
5
+ import { redisService } from '../services/redis.js';
6
+ import { storageService } from '../services/storage.js';
7
+ import { eInvoiceService } from '../services/eInvoice.js';
8
+ export const renderRoutes = async (fastify) => {
9
+ // Helper to authenticate Bearer token and check monthly quota
10
+ async function authenticateAndCheckQuota(request, reply) {
11
+ const authHeader = request.headers.authorization;
12
+ let token = null;
13
+ if (authHeader && authHeader.startsWith('Bearer ')) {
14
+ token = authHeader.replace(/^Bearer\s+/i, '').trim();
15
+ }
16
+ else if (env.NODE_ENV === 'development') {
17
+ token = env.DEV_DEFAULT_API_KEY || 'sec_live_dev_test_key_12345';
18
+ }
19
+ if (!token) {
20
+ reply.code(401).send({
21
+ statusCode: 401,
22
+ error: 'Unauthorized',
23
+ message: 'Missing or invalid Bearer authentication token. Example header: Authorization: Bearer sec_live_dev_test_key_12345',
24
+ });
25
+ return null;
26
+ }
27
+ const apiKey = await redisService.getApiKey(token);
28
+ if (!apiKey) {
29
+ reply.code(401).send({
30
+ statusCode: 401,
31
+ error: 'Unauthorized',
32
+ message: 'Invalid API key provided',
33
+ });
34
+ return null;
35
+ }
36
+ if (apiKey.status !== 'active') {
37
+ reply.code(401).send({
38
+ statusCode: 401,
39
+ error: 'Unauthorized',
40
+ message: 'API key is inactive or subscription has expired',
41
+ });
42
+ return null;
43
+ }
44
+ const quota = await redisService.incrementAndCheckQuota(token, apiKey.monthly_limit);
45
+ reply.header('X-RateLimit-Limit', String(quota.limit));
46
+ reply.header('X-RateLimit-Remaining', String(quota.remaining));
47
+ if (!quota.allowed) {
48
+ reply.code(429).send({
49
+ statusCode: 429,
50
+ error: 'Too Many Requests',
51
+ message: `Monthly quota exceeded (${quota.currentUsage}/${quota.limit} renders). Please upgrade your plan.`,
52
+ });
53
+ return null;
54
+ }
55
+ return { token, apiKey, quota };
56
+ }
57
+ // Canonical POST /v1/render/pdf (Documented in OpenAPI / Scalar)
58
+ fastify.post('/v1/render/pdf', {
59
+ schema: {
60
+ tags: ['Rendering'],
61
+ summary: 'Render HTML with Tailwind to PDF',
62
+ description: 'Converts provided HTML containing Tailwind CSS classes into a high-fidelity PDF document.',
63
+ security: [{ bearerAuth: [] }],
64
+ body: RenderPdfRequestSchema,
65
+ response: {
66
+ 200: z.any().describe('Binary PDF stream (application/pdf)'),
67
+ 400: ErrorResponseSchema,
68
+ 401: ErrorResponseSchema,
69
+ 429: ErrorResponseSchema,
70
+ 500: ErrorResponseSchema,
71
+ 504: ErrorResponseSchema,
72
+ },
73
+ },
74
+ }, async (request, reply) => {
75
+ const auth = await authenticateAndCheckQuota(request, reply);
76
+ if (!auth)
77
+ return;
78
+ const pdfBuffer = await browserService.renderPdf(request.body);
79
+ if (request.body.store) {
80
+ const stored = await storageService.saveFile(pdfBuffer, {
81
+ mimeType: 'application/pdf',
82
+ extension: 'pdf',
83
+ filename: 'document.pdf',
84
+ expiresIn: request.body.expires_in,
85
+ });
86
+ return reply
87
+ .code(200)
88
+ .header('X-Tailprint-Hosted-Url', stored.hostedUrl)
89
+ .send({
90
+ success: true,
91
+ hostedUrl: stored.hostedUrl,
92
+ fileId: stored.fileId,
93
+ filename: stored.filename,
94
+ mimeType: stored.mimeType,
95
+ sizeBytes: stored.sizeBytes,
96
+ expiresAt: stored.expiresAt,
97
+ });
98
+ }
99
+ return reply
100
+ .code(200)
101
+ .header('Content-Type', 'application/pdf')
102
+ .header('Content-Disposition', 'inline; filename="rendered.pdf"')
103
+ .header('Content-Length', String(pdfBuffer.length))
104
+ .send(pdfBuffer);
105
+ });
106
+ // Alias POST /render/pdf (Hidden from OpenAPI docs to prevent duplicates)
107
+ fastify.post('/render/pdf', {
108
+ schema: {
109
+ hide: true,
110
+ body: RenderPdfRequestSchema,
111
+ },
112
+ }, async (request, reply) => {
113
+ const auth = await authenticateAndCheckQuota(request, reply);
114
+ if (!auth)
115
+ return;
116
+ const pdfBuffer = await browserService.renderPdf(request.body);
117
+ if (request.body.store) {
118
+ const stored = await storageService.saveFile(pdfBuffer, {
119
+ mimeType: 'application/pdf',
120
+ extension: 'pdf',
121
+ filename: 'document.pdf',
122
+ expiresIn: request.body.expires_in,
123
+ });
124
+ return reply
125
+ .code(200)
126
+ .header('X-Tailprint-Hosted-Url', stored.hostedUrl)
127
+ .send({
128
+ success: true,
129
+ hostedUrl: stored.hostedUrl,
130
+ fileId: stored.fileId,
131
+ filename: stored.filename,
132
+ mimeType: stored.mimeType,
133
+ sizeBytes: stored.sizeBytes,
134
+ expiresAt: stored.expiresAt,
135
+ });
136
+ }
137
+ return reply
138
+ .code(200)
139
+ .header('Content-Type', 'application/pdf')
140
+ .header('Content-Disposition', 'inline; filename="rendered.pdf"')
141
+ .header('Content-Length', String(pdfBuffer.length))
142
+ .send(pdfBuffer);
143
+ });
144
+ // Canonical POST /v1/render/image (Documented in OpenAPI / Scalar)
145
+ fastify.post('/v1/render/image', {
146
+ schema: {
147
+ tags: ['Rendering'],
148
+ summary: 'Render HTML with Tailwind to Open Graph / Image',
149
+ description: 'Generates an optimized PNG or JPEG image from HTML containing Tailwind CSS classes. Results are hashed and cached.',
150
+ security: [{ bearerAuth: [] }],
151
+ body: RenderImageRequestSchema,
152
+ response: {
153
+ 200: z.any().describe('Binary image stream (image/png or image/jpeg)'),
154
+ 400: ErrorResponseSchema,
155
+ 401: ErrorResponseSchema,
156
+ 429: ErrorResponseSchema,
157
+ 500: ErrorResponseSchema,
158
+ 504: ErrorResponseSchema,
159
+ },
160
+ },
161
+ }, async (request, reply) => {
162
+ const auth = await authenticateAndCheckQuota(request, reply);
163
+ if (!auth)
164
+ return;
165
+ const { html, url, width, height, type, wait_for, delay_ms } = request.body;
166
+ const mimeType = type === 'jpeg' ? 'image/jpeg' : 'image/png';
167
+ // 1. Generate SHA-256 cache key
168
+ const cacheHash = redisService.generateImageCacheHash({
169
+ html,
170
+ url,
171
+ width,
172
+ height,
173
+ type,
174
+ wait_for,
175
+ delay_ms,
176
+ });
177
+ // 2. Check cache
178
+ const cached = await redisService.getCachedImage(cacheHash);
179
+ if (cached) {
180
+ if (request.body.store) {
181
+ const stored = await storageService.saveFile(cached, {
182
+ mimeType,
183
+ extension: type || 'png',
184
+ filename: `render.${type || 'png'}`,
185
+ expiresIn: request.body.expires_in,
186
+ });
187
+ return reply
188
+ .code(200)
189
+ .header('X-Cache', 'HIT')
190
+ .header('X-Tailprint-Hosted-Url', stored.hostedUrl)
191
+ .send({
192
+ success: true,
193
+ hostedUrl: stored.hostedUrl,
194
+ fileId: stored.fileId,
195
+ filename: stored.filename,
196
+ mimeType: stored.mimeType,
197
+ sizeBytes: stored.sizeBytes,
198
+ expiresAt: stored.expiresAt,
199
+ });
200
+ }
201
+ return reply
202
+ .code(200)
203
+ .header('Content-Type', mimeType)
204
+ .header('X-Cache', 'HIT')
205
+ .header('Content-Length', String(cached.length))
206
+ .send(cached);
207
+ }
208
+ // 3. Render and cache image
209
+ const imageBuffer = await browserService.renderImage(request.body);
210
+ await redisService.setCachedImage(cacheHash, imageBuffer);
211
+ if (request.body.store) {
212
+ const stored = await storageService.saveFile(imageBuffer, {
213
+ mimeType,
214
+ extension: type || 'png',
215
+ filename: `render.${type || 'png'}`,
216
+ expiresIn: request.body.expires_in,
217
+ });
218
+ return reply
219
+ .code(200)
220
+ .header('X-Cache', 'MISS')
221
+ .header('X-Tailprint-Hosted-Url', stored.hostedUrl)
222
+ .send({
223
+ success: true,
224
+ hostedUrl: stored.hostedUrl,
225
+ fileId: stored.fileId,
226
+ filename: stored.filename,
227
+ mimeType: stored.mimeType,
228
+ sizeBytes: stored.sizeBytes,
229
+ expiresAt: stored.expiresAt,
230
+ });
231
+ }
232
+ return reply
233
+ .code(200)
234
+ .header('Content-Type', mimeType)
235
+ .header('X-Cache', 'MISS')
236
+ .header('Content-Length', String(imageBuffer.length))
237
+ .send(imageBuffer);
238
+ });
239
+ // Alias POST /render/image (Hidden from OpenAPI docs to prevent duplicates)
240
+ fastify.post('/render/image', {
241
+ schema: {
242
+ hide: true,
243
+ body: RenderImageRequestSchema,
244
+ },
245
+ }, async (request, reply) => {
246
+ const auth = await authenticateAndCheckQuota(request, reply);
247
+ if (!auth)
248
+ return;
249
+ const { html, url, width, height, type, wait_for, delay_ms } = request.body;
250
+ const mimeType = type === 'jpeg' ? 'image/jpeg' : 'image/png';
251
+ const cacheHash = redisService.generateImageCacheHash({
252
+ html,
253
+ url,
254
+ width,
255
+ height,
256
+ type,
257
+ wait_for,
258
+ delay_ms,
259
+ });
260
+ const cached = await redisService.getCachedImage(cacheHash);
261
+ if (cached) {
262
+ if (request.body.store) {
263
+ const stored = await storageService.saveFile(cached, {
264
+ mimeType,
265
+ extension: type || 'png',
266
+ filename: `render.${type || 'png'}`,
267
+ expiresIn: request.body.expires_in,
268
+ });
269
+ return reply
270
+ .code(200)
271
+ .header('X-Cache', 'HIT')
272
+ .header('X-Tailprint-Hosted-Url', stored.hostedUrl)
273
+ .send({
274
+ success: true,
275
+ hostedUrl: stored.hostedUrl,
276
+ fileId: stored.fileId,
277
+ filename: stored.filename,
278
+ mimeType: stored.mimeType,
279
+ sizeBytes: stored.sizeBytes,
280
+ expiresAt: stored.expiresAt,
281
+ });
282
+ }
283
+ return reply
284
+ .code(200)
285
+ .header('Content-Type', mimeType)
286
+ .header('X-Cache', 'HIT')
287
+ .header('Content-Length', String(cached.length))
288
+ .send(cached);
289
+ }
290
+ const imageBuffer = await browserService.renderImage(request.body);
291
+ await redisService.setCachedImage(cacheHash, imageBuffer);
292
+ if (request.body.store) {
293
+ const stored = await storageService.saveFile(imageBuffer, {
294
+ mimeType,
295
+ extension: type || 'png',
296
+ filename: `render.${type || 'png'}`,
297
+ expiresIn: request.body.expires_in,
298
+ });
299
+ return reply
300
+ .code(200)
301
+ .header('X-Cache', 'MISS')
302
+ .header('X-Tailprint-Hosted-Url', stored.hostedUrl)
303
+ .send({
304
+ success: true,
305
+ hostedUrl: stored.hostedUrl,
306
+ fileId: stored.fileId,
307
+ filename: stored.filename,
308
+ mimeType: stored.mimeType,
309
+ sizeBytes: stored.sizeBytes,
310
+ expiresAt: stored.expiresAt,
311
+ });
312
+ }
313
+ return reply
314
+ .code(200)
315
+ .header('Content-Type', mimeType)
316
+ .header('X-Cache', 'MISS')
317
+ .header('Content-Length', String(imageBuffer.length))
318
+ .send(imageBuffer);
319
+ });
320
+ // POST /v1/render/einvoice - Factur-X / PEPPOL European E-Invoicing
321
+ fastify.post('/v1/render/einvoice', {
322
+ schema: {
323
+ tags: ['Rendering'],
324
+ summary: 'Generate European Hybrid E-Invoice (Factur-X / ZUGFeRD / EN 16931)',
325
+ description: 'Generates an EU-compliant hybrid electronic invoice conforming to EN 16931 / Factur-X / ZUGFeRD 2.2. Automatically creates CrossIndustryInvoice XML, attaches it directly to the PDF catalog, and produces a print-ready visual invoice. Saves with uncompressed object streams for maximum compatibility with ERP and legacy accounting systems.',
326
+ body: RenderEInvoiceRequestSchema,
327
+ response: {
328
+ 200: z.union([
329
+ HostedFileResponseSchema,
330
+ z.any().describe('Binary hybrid PDF stream (application/pdf)'),
331
+ ]),
332
+ 400: ErrorResponseSchema,
333
+ 401: ErrorResponseSchema,
334
+ 429: ErrorResponseSchema,
335
+ 500: ErrorResponseSchema,
336
+ },
337
+ },
338
+ }, async (request, reply) => {
339
+ const auth = await authenticateAndCheckQuota(request, reply);
340
+ if (!auth)
341
+ return;
342
+ const body = request.body;
343
+ const result = await eInvoiceService.generateHybridInvoice({
344
+ invoiceNumber: body.invoiceNumber,
345
+ issueDate: body.issueDate,
346
+ dueDate: body.dueDate,
347
+ currency: body.currency,
348
+ seller: body.seller,
349
+ buyer: body.buyer,
350
+ lineItems: body.lineItems,
351
+ paymentIban: body.paymentIban,
352
+ paymentBic: body.paymentBic,
353
+ notes: body.notes,
354
+ profile: body.profile,
355
+ expiresIn: body.expires_in,
356
+ });
357
+ if (body.store && result.pdfStored) {
358
+ return reply
359
+ .code(200)
360
+ .header('X-Tailprint-Hosted-Url', result.pdfStored.hostedUrl)
361
+ .header('X-Invoice-Total', String(result.grossTotal))
362
+ .send({
363
+ success: true,
364
+ hostedUrl: result.pdfStored.hostedUrl,
365
+ fileId: result.pdfStored.fileId,
366
+ filename: result.pdfStored.filename,
367
+ mimeType: result.pdfStored.mimeType,
368
+ sizeBytes: result.pdfStored.sizeBytes,
369
+ expiresAt: result.pdfStored.expiresAt,
370
+ });
371
+ }
372
+ return reply
373
+ .code(200)
374
+ .header('Content-Type', 'application/pdf')
375
+ .header('Content-Disposition', `inline; filename="factur-x-${result.invoiceNumber}.pdf"`)
376
+ .header('Content-Length', String(result.pdfBuffer.length))
377
+ .header('X-Invoice-Total', String(result.grossTotal))
378
+ .header('X-Invoice-Currency', result.currency)
379
+ .header('X-Tailprint-Hosted-Url', result.pdfStored?.hostedUrl || '')
380
+ .send(result.pdfBuffer);
381
+ });
382
+ // Public Hosted Delivery Endpoint: GET /d/:fileId
383
+ fastify.get('/d/:fileId', {
384
+ schema: {
385
+ tags: ['Hosted Delivery'],
386
+ summary: 'Access hosted PDF or image document',
387
+ description: 'Serves an ephemeral or hosted document generated with store: true. Cached with immutable CDN headers.',
388
+ params: z.object({
389
+ fileId: z.string().describe('File ID returned from hosted render'),
390
+ }),
391
+ querystring: z.object({
392
+ download: z.coerce.boolean().optional().describe('Force attachment download instead of inline browser display'),
393
+ }),
394
+ },
395
+ }, async (request, reply) => {
396
+ const { fileId } = request.params;
397
+ const { download } = request.query || {};
398
+ const file = await storageService.getFile(fileId);
399
+ if (!file) {
400
+ return reply.code(404).send({
401
+ statusCode: 404,
402
+ error: 'Not Found',
403
+ message: 'The requested hosted document does not exist or has expired.',
404
+ });
405
+ }
406
+ const disposition = download ? 'attachment' : 'inline';
407
+ return reply
408
+ .code(200)
409
+ .header('Content-Type', file.metadata.mimeType)
410
+ .header('Content-Length', String(file.buffer.length))
411
+ .header('Content-Disposition', `${disposition}; filename="${file.metadata.filename}"`)
412
+ .header('Cache-Control', 'public, max-age=86400, immutable')
413
+ .header('X-Tailprint-Expires-At', file.metadata.expiresAt)
414
+ .send(file.buffer);
415
+ });
416
+ // Public Interactive Playground Route for the Landing Page
417
+ fastify.post('/v1/render/playground', {
418
+ schema: {
419
+ hide: true,
420
+ body: RenderPdfRequestSchema,
421
+ },
422
+ }, async (request, reply) => {
423
+ const clientIp = request.ip || 'anonymous';
424
+ const rateLimit = await redisService.checkPlaygroundRateLimit(clientIp);
425
+ if (!rateLimit.allowed) {
426
+ return reply.code(429).send({
427
+ statusCode: 429,
428
+ error: 'Too Many Requests',
429
+ message: 'Playground rate limit exceeded (max 25 renders/hour). Please upgrade to an official plan for unlimited renders.',
430
+ });
431
+ }
432
+ const startTime = Date.now();
433
+ const pdfBuffer = await browserService.renderPdf(request.body);
434
+ const renderDuration = Date.now() - startTime;
435
+ return reply
436
+ .code(200)
437
+ .header('Content-Type', 'application/pdf')
438
+ .header('Content-Disposition', 'inline; filename="playground-preview.pdf"')
439
+ .header('X-Render-Time-Ms', String(renderDuration))
440
+ .header('X-Playground-Remaining', String(rateLimit.remaining))
441
+ .send(pdfBuffer);
442
+ });
443
+ };
444
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/routes/render.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,EACL,sBAAsB,EACtB,wBAAwB,EACxB,2BAA2B,EAE3B,wBAAwB,EACxB,mBAAmB,GACpB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAE1D,MAAM,CAAC,MAAM,YAAY,GAA0B,KAAK,EAAE,OAAO,EAAE,EAAE;IACnE,8DAA8D;IAC9D,KAAK,UAAU,yBAAyB,CAAC,OAAY,EAAE,KAAU;QAC/D,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC;QACjD,IAAI,KAAK,GAAkB,IAAI,CAAC;QAEhC,IAAI,UAAU,IAAI,UAAU,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YACnD,KAAK,GAAG,UAAU,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACvD,CAAC;aAAM,IAAI,GAAG,CAAC,QAAQ,KAAK,aAAa,EAAE,CAAC;YAC1C,KAAK,GAAG,GAAG,CAAC,mBAAmB,IAAI,6BAA6B,CAAC;QACnE,CAAC;QAED,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBACnB,UAAU,EAAE,GAAG;gBACf,KAAK,EAAE,cAAc;gBACrB,OAAO,EAAE,mHAAmH;aAC7H,CAAC,CAAC;YACH,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACnD,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBACnB,UAAU,EAAE,GAAG;gBACf,KAAK,EAAE,cAAc;gBACrB,OAAO,EAAE,0BAA0B;aACpC,CAAC,CAAC;YACH,OAAO,IAAI,CAAC;QACd,CAAC;QAED,IAAI,MAAM,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC/B,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBACnB,UAAU,EAAE,GAAG;gBACf,KAAK,EAAE,cAAc;gBACrB,OAAO,EAAE,iDAAiD;aAC3D,CAAC,CAAC;YACH,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,KAAK,GAAG,MAAM,YAAY,CAAC,sBAAsB,CAAC,KAAK,EAAE,MAAM,CAAC,aAAa,CAAC,CAAC;QACrF,KAAK,CAAC,MAAM,CAAC,mBAAmB,EAAE,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;QACvD,KAAK,CAAC,MAAM,CAAC,uBAAuB,EAAE,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC;QAE/D,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;YACnB,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBACnB,UAAU,EAAE,GAAG;gBACf,KAAK,EAAE,mBAAmB;gBAC1B,OAAO,EAAE,2BAA2B,KAAK,CAAC,YAAY,IAAI,KAAK,CAAC,KAAK,sCAAsC;aAC5G,CAAC,CAAC;YACH,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAClC,CAAC;IAED,iEAAiE;IACjE,OAAO,CAAC,IAAI,CACV,gBAAgB,EAChB;QACE,MAAM,EAAE;YACN,IAAI,EAAE,CAAC,WAAW,CAAC;YACnB,OAAO,EAAE,kCAAkC;YAC3C,WAAW,EAAE,2FAA2F;YACxG,QAAQ,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC;YAC9B,IAAI,EAAE,sBAAsB;YAC5B,QAAQ,EAAE;gBACR,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,qCAAqC,CAAC;gBAC5D,GAAG,EAAE,mBAAmB;gBACxB,GAAG,EAAE,mBAAmB;gBACxB,GAAG,EAAE,mBAAmB;gBACxB,GAAG,EAAE,mBAAmB;gBACxB,GAAG,EAAE,mBAAmB;aACzB;SACF;KACF,EACD,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;QACvB,MAAM,IAAI,GAAG,MAAM,yBAAyB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QAC7D,IAAI,CAAC,IAAI;YAAE,OAAO;QAElB,MAAM,SAAS,GAAG,MAAM,cAAc,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAE/D,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;YACvB,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,QAAQ,CAAC,SAAS,EAAE;gBACtD,QAAQ,EAAE,iBAAiB;gBAC3B,SAAS,EAAE,KAAK;gBAChB,QAAQ,EAAE,cAAc;gBACxB,SAAS,EAAE,OAAO,CAAC,IAAI,CAAC,UAAU;aACnC,CAAC,CAAC;YAEH,OAAO,KAAK;iBACT,IAAI,CAAC,GAAG,CAAC;iBACT,MAAM,CAAC,wBAAwB,EAAE,MAAM,CAAC,SAAS,CAAC;iBAClD,IAAI,CAAC;gBACJ,OAAO,EAAE,IAAI;gBACb,SAAS,EAAE,MAAM,CAAC,SAAS;gBAC3B,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,QAAQ,EAAE,MAAM,CAAC,QAAQ;gBACzB,QAAQ,EAAE,MAAM,CAAC,QAAQ;gBACzB,SAAS,EAAE,MAAM,CAAC,SAAS;gBAC3B,SAAS,EAAE,MAAM,CAAC,SAAS;aAC5B,CAAC,CAAC;QACP,CAAC;QAED,OAAO,KAAK;aACT,IAAI,CAAC,GAAG,CAAC;aACT,MAAM,CAAC,cAAc,EAAE,iBAAiB,CAAC;aACzC,MAAM,CAAC,qBAAqB,EAAE,iCAAiC,CAAC;aAChE,MAAM,CAAC,gBAAgB,EAAE,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;aAClD,IAAI,CAAC,SAAS,CAAC,CAAC;IACrB,CAAC,CACF,CAAC;IAEF,0EAA0E;IAC1E,OAAO,CAAC,IAAI,CACV,aAAa,EACb;QACE,MAAM,EAAE;YACN,IAAI,EAAE,IAAI;YACV,IAAI,EAAE,sBAAsB;SAC7B;KACF,EACD,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;QACvB,MAAM,IAAI,GAAG,MAAM,yBAAyB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QAC7D,IAAI,CAAC,IAAI;YAAE,OAAO;QAElB,MAAM,SAAS,GAAG,MAAM,cAAc,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAE/D,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;YACvB,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,QAAQ,CAAC,SAAS,EAAE;gBACtD,QAAQ,EAAE,iBAAiB;gBAC3B,SAAS,EAAE,KAAK;gBAChB,QAAQ,EAAE,cAAc;gBACxB,SAAS,EAAE,OAAO,CAAC,IAAI,CAAC,UAAU;aACnC,CAAC,CAAC;YAEH,OAAO,KAAK;iBACT,IAAI,CAAC,GAAG,CAAC;iBACT,MAAM,CAAC,wBAAwB,EAAE,MAAM,CAAC,SAAS,CAAC;iBAClD,IAAI,CAAC;gBACJ,OAAO,EAAE,IAAI;gBACb,SAAS,EAAE,MAAM,CAAC,SAAS;gBAC3B,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,QAAQ,EAAE,MAAM,CAAC,QAAQ;gBACzB,QAAQ,EAAE,MAAM,CAAC,QAAQ;gBACzB,SAAS,EAAE,MAAM,CAAC,SAAS;gBAC3B,SAAS,EAAE,MAAM,CAAC,SAAS;aAC5B,CAAC,CAAC;QACP,CAAC;QAED,OAAO,KAAK;aACT,IAAI,CAAC,GAAG,CAAC;aACT,MAAM,CAAC,cAAc,EAAE,iBAAiB,CAAC;aACzC,MAAM,CAAC,qBAAqB,EAAE,iCAAiC,CAAC;aAChE,MAAM,CAAC,gBAAgB,EAAE,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;aAClD,IAAI,CAAC,SAAS,CAAC,CAAC;IACrB,CAAC,CACF,CAAC;IAEF,mEAAmE;IACnE,OAAO,CAAC,IAAI,CACV,kBAAkB,EAClB;QACE,MAAM,EAAE;YACN,IAAI,EAAE,CAAC,WAAW,CAAC;YACnB,OAAO,EAAE,iDAAiD;YAC1D,WAAW,EAAE,oHAAoH;YACjI,QAAQ,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC;YAC9B,IAAI,EAAE,wBAAwB;YAC9B,QAAQ,EAAE;gBACR,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,+CAA+C,CAAC;gBACtE,GAAG,EAAE,mBAAmB;gBACxB,GAAG,EAAE,mBAAmB;gBACxB,GAAG,EAAE,mBAAmB;gBACxB,GAAG,EAAE,mBAAmB;gBACxB,GAAG,EAAE,mBAAmB;aACzB;SACF;KACF,EACD,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;QACvB,MAAM,IAAI,GAAG,MAAM,yBAAyB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QAC7D,IAAI,CAAC,IAAI;YAAE,OAAO;QAElB,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;QAC5E,MAAM,QAAQ,GAAG,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,WAAW,CAAC;QAE9D,gCAAgC;QAChC,MAAM,SAAS,GAAG,YAAY,CAAC,sBAAsB,CAAC;YACpD,IAAI;YACJ,GAAG;YACH,KAAK;YACL,MAAM;YACN,IAAI;YACJ,QAAQ;YACR,QAAQ;SACT,CAAC,CAAC;QAEH,iBAAiB;QACjB,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC;QAC5D,IAAI,MAAM,EAAE,CAAC;YACX,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;gBACvB,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,QAAQ,CAAC,MAAM,EAAE;oBACnD,QAAQ;oBACR,SAAS,EAAE,IAAI,IAAI,KAAK;oBACxB,QAAQ,EAAE,UAAU,IAAI,IAAI,KAAK,EAAE;oBACnC,SAAS,EAAE,OAAO,CAAC,IAAI,CAAC,UAAU;iBACnC,CAAC,CAAC;gBAEH,OAAO,KAAK;qBACT,IAAI,CAAC,GAAG,CAAC;qBACT,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC;qBACxB,MAAM,CAAC,wBAAwB,EAAE,MAAM,CAAC,SAAS,CAAC;qBAClD,IAAI,CAAC;oBACJ,OAAO,EAAE,IAAI;oBACb,SAAS,EAAE,MAAM,CAAC,SAAS;oBAC3B,MAAM,EAAE,MAAM,CAAC,MAAM;oBACrB,QAAQ,EAAE,MAAM,CAAC,QAAQ;oBACzB,QAAQ,EAAE,MAAM,CAAC,QAAQ;oBACzB,SAAS,EAAE,MAAM,CAAC,SAAS;oBAC3B,SAAS,EAAE,MAAM,CAAC,SAAS;iBAC5B,CAAC,CAAC;YACP,CAAC;YAED,OAAO,KAAK;iBACT,IAAI,CAAC,GAAG,CAAC;iBACT,MAAM,CAAC,cAAc,EAAE,QAAQ,CAAC;iBAChC,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC;iBACxB,MAAM,CAAC,gBAAgB,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;iBAC/C,IAAI,CAAC,MAAM,CAAC,CAAC;QAClB,CAAC;QAED,4BAA4B;QAC5B,MAAM,WAAW,GAAG,MAAM,cAAc,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACnE,MAAM,YAAY,CAAC,cAAc,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;QAE1D,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;YACvB,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,QAAQ,CAAC,WAAW,EAAE;gBACxD,QAAQ;gBACR,SAAS,EAAE,IAAI,IAAI,KAAK;gBACxB,QAAQ,EAAE,UAAU,IAAI,IAAI,KAAK,EAAE;gBACnC,SAAS,EAAE,OAAO,CAAC,IAAI,CAAC,UAAU;aACnC,CAAC,CAAC;YAEH,OAAO,KAAK;iBACT,IAAI,CAAC,GAAG,CAAC;iBACT,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC;iBACzB,MAAM,CAAC,wBAAwB,EAAE,MAAM,CAAC,SAAS,CAAC;iBAClD,IAAI,CAAC;gBACJ,OAAO,EAAE,IAAI;gBACb,SAAS,EAAE,MAAM,CAAC,SAAS;gBAC3B,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,QAAQ,EAAE,MAAM,CAAC,QAAQ;gBACzB,QAAQ,EAAE,MAAM,CAAC,QAAQ;gBACzB,SAAS,EAAE,MAAM,CAAC,SAAS;gBAC3B,SAAS,EAAE,MAAM,CAAC,SAAS;aAC5B,CAAC,CAAC;QACP,CAAC;QAED,OAAO,KAAK;aACT,IAAI,CAAC,GAAG,CAAC;aACT,MAAM,CAAC,cAAc,EAAE,QAAQ,CAAC;aAChC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC;aACzB,MAAM,CAAC,gBAAgB,EAAE,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;aACpD,IAAI,CAAC,WAAW,CAAC,CAAC;IACvB,CAAC,CACF,CAAC;IAEF,4EAA4E;IAC5E,OAAO,CAAC,IAAI,CACV,eAAe,EACf;QACE,MAAM,EAAE;YACN,IAAI,EAAE,IAAI;YACV,IAAI,EAAE,wBAAwB;SAC/B;KACF,EACD,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;QACvB,MAAM,IAAI,GAAG,MAAM,yBAAyB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QAC7D,IAAI,CAAC,IAAI;YAAE,OAAO;QAElB,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;QAC5E,MAAM,QAAQ,GAAG,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,WAAW,CAAC;QAE9D,MAAM,SAAS,GAAG,YAAY,CAAC,sBAAsB,CAAC;YACpD,IAAI;YACJ,GAAG;YACH,KAAK;YACL,MAAM;YACN,IAAI;YACJ,QAAQ;YACR,QAAQ;SACT,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC;QAC5D,IAAI,MAAM,EAAE,CAAC;YACX,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;gBACvB,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,QAAQ,CAAC,MAAM,EAAE;oBACnD,QAAQ;oBACR,SAAS,EAAE,IAAI,IAAI,KAAK;oBACxB,QAAQ,EAAE,UAAU,IAAI,IAAI,KAAK,EAAE;oBACnC,SAAS,EAAE,OAAO,CAAC,IAAI,CAAC,UAAU;iBACnC,CAAC,CAAC;gBAEH,OAAO,KAAK;qBACT,IAAI,CAAC,GAAG,CAAC;qBACT,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC;qBACxB,MAAM,CAAC,wBAAwB,EAAE,MAAM,CAAC,SAAS,CAAC;qBAClD,IAAI,CAAC;oBACJ,OAAO,EAAE,IAAI;oBACb,SAAS,EAAE,MAAM,CAAC,SAAS;oBAC3B,MAAM,EAAE,MAAM,CAAC,MAAM;oBACrB,QAAQ,EAAE,MAAM,CAAC,QAAQ;oBACzB,QAAQ,EAAE,MAAM,CAAC,QAAQ;oBACzB,SAAS,EAAE,MAAM,CAAC,SAAS;oBAC3B,SAAS,EAAE,MAAM,CAAC,SAAS;iBAC5B,CAAC,CAAC;YACP,CAAC;YAED,OAAO,KAAK;iBACT,IAAI,CAAC,GAAG,CAAC;iBACT,MAAM,CAAC,cAAc,EAAE,QAAQ,CAAC;iBAChC,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC;iBACxB,MAAM,CAAC,gBAAgB,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;iBAC/C,IAAI,CAAC,MAAM,CAAC,CAAC;QAClB,CAAC;QAED,MAAM,WAAW,GAAG,MAAM,cAAc,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACnE,MAAM,YAAY,CAAC,cAAc,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;QAE1D,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;YACvB,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,QAAQ,CAAC,WAAW,EAAE;gBACxD,QAAQ;gBACR,SAAS,EAAE,IAAI,IAAI,KAAK;gBACxB,QAAQ,EAAE,UAAU,IAAI,IAAI,KAAK,EAAE;gBACnC,SAAS,EAAE,OAAO,CAAC,IAAI,CAAC,UAAU;aACnC,CAAC,CAAC;YAEH,OAAO,KAAK;iBACT,IAAI,CAAC,GAAG,CAAC;iBACT,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC;iBACzB,MAAM,CAAC,wBAAwB,EAAE,MAAM,CAAC,SAAS,CAAC;iBAClD,IAAI,CAAC;gBACJ,OAAO,EAAE,IAAI;gBACb,SAAS,EAAE,MAAM,CAAC,SAAS;gBAC3B,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,QAAQ,EAAE,MAAM,CAAC,QAAQ;gBACzB,QAAQ,EAAE,MAAM,CAAC,QAAQ;gBACzB,SAAS,EAAE,MAAM,CAAC,SAAS;gBAC3B,SAAS,EAAE,MAAM,CAAC,SAAS;aAC5B,CAAC,CAAC;QACP,CAAC;QAED,OAAO,KAAK;aACT,IAAI,CAAC,GAAG,CAAC;aACT,MAAM,CAAC,cAAc,EAAE,QAAQ,CAAC;aAChC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC;aACzB,MAAM,CAAC,gBAAgB,EAAE,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;aACpD,IAAI,CAAC,WAAW,CAAC,CAAC;IACvB,CAAC,CACF,CAAC;IAEF,oEAAoE;IACpE,OAAO,CAAC,IAAI,CACV,qBAAqB,EACrB;QACE,MAAM,EAAE;YACN,IAAI,EAAE,CAAC,WAAW,CAAC;YACnB,OAAO,EAAE,oEAAoE;YAC7E,WAAW,EAAE,mVAAmV;YAChW,IAAI,EAAE,2BAA2B;YACjC,QAAQ,EAAE;gBACR,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC;oBACX,wBAAwB;oBACxB,CAAC,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,4CAA4C,CAAC;iBAC/D,CAAC;gBACF,GAAG,EAAE,mBAAmB;gBACxB,GAAG,EAAE,mBAAmB;gBACxB,GAAG,EAAE,mBAAmB;gBACxB,GAAG,EAAE,mBAAmB;aACzB;SACF;KACF,EACD,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;QACvB,MAAM,IAAI,GAAG,MAAM,yBAAyB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QAC7D,IAAI,CAAC,IAAI;YAAE,OAAO;QAElB,MAAM,IAAI,GAAG,OAAO,CAAC,IAA4B,CAAC;QAClD,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,qBAAqB,CAAC;YACzD,aAAa,EAAE,IAAI,CAAC,aAAa;YACjC,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,SAAS,EAAE,IAAI,CAAC,UAAU;SAC3B,CAAC,CAAC;QAEH,IAAI,IAAI,CAAC,KAAK,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;YACnC,OAAO,KAAK;iBACT,IAAI,CAAC,GAAG,CAAC;iBACT,MAAM,CAAC,wBAAwB,EAAE,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC;iBAC5D,MAAM,CAAC,iBAAiB,EAAE,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;iBACpD,IAAI,CAAC;gBACJ,OAAO,EAAE,IAAI;gBACb,SAAS,EAAE,MAAM,CAAC,SAAS,CAAC,SAAS;gBACrC,MAAM,EAAE,MAAM,CAAC,SAAS,CAAC,MAAM;gBAC/B,QAAQ,EAAE,MAAM,CAAC,SAAS,CAAC,QAAQ;gBACnC,QAAQ,EAAE,MAAM,CAAC,SAAS,CAAC,QAAQ;gBACnC,SAAS,EAAE,MAAM,CAAC,SAAS,CAAC,SAAS;gBACrC,SAAS,EAAE,MAAM,CAAC,SAAS,CAAC,SAAS;aACtC,CAAC,CAAC;QACP,CAAC;QAED,OAAO,KAAK;aACT,IAAI,CAAC,GAAG,CAAC;aACT,MAAM,CAAC,cAAc,EAAE,iBAAiB,CAAC;aACzC,MAAM,CAAC,qBAAqB,EAAE,8BAA8B,MAAM,CAAC,aAAa,OAAO,CAAC;aACxF,MAAM,CAAC,gBAAgB,EAAE,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;aACzD,MAAM,CAAC,iBAAiB,EAAE,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;aACpD,MAAM,CAAC,oBAAoB,EAAE,MAAM,CAAC,QAAQ,CAAC;aAC7C,MAAM,CAAC,wBAAwB,EAAE,MAAM,CAAC,SAAS,EAAE,SAAS,IAAI,EAAE,CAAC;aACnE,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IAC5B,CAAC,CACF,CAAC;IAEF,kDAAkD;IAClD,OAAO,CAAC,GAAG,CACT,YAAY,EACZ;QACE,MAAM,EAAE;YACN,IAAI,EAAE,CAAC,iBAAiB,CAAC;YACzB,OAAO,EAAE,qCAAqC;YAC9C,WAAW,EAAE,uGAAuG;YACpH,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC;gBACf,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,qCAAqC,CAAC;aACnE,CAAC;YACF,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;gBACpB,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,6DAA6D,CAAC;aAChH,CAAC;SACH;KACF,EACD,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;QACvB,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAA4B,CAAC;QACxD,MAAM,EAAE,QAAQ,EAAE,GAAI,OAAO,CAAC,KAAgC,IAAI,EAAE,CAAC;QAErE,MAAM,IAAI,GAAG,MAAM,cAAc,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAClD,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBAC1B,UAAU,EAAE,GAAG;gBACf,KAAK,EAAE,WAAW;gBAClB,OAAO,EAAE,8DAA8D;aACxE,CAAC,CAAC;QACL,CAAC;QAED,MAAM,WAAW,GAAG,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,QAAQ,CAAC;QAEvD,OAAO,KAAK;aACT,IAAI,CAAC,GAAG,CAAC;aACT,MAAM,CAAC,cAAc,EAAE,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;aAC9C,MAAM,CAAC,gBAAgB,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;aACpD,MAAM,CAAC,qBAAqB,EAAE,GAAG,WAAW,eAAe,IAAI,CAAC,QAAQ,CAAC,QAAQ,GAAG,CAAC;aACrF,MAAM,CAAC,eAAe,EAAE,kCAAkC,CAAC;aAC3D,MAAM,CAAC,wBAAwB,EAAE,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC;aACzD,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACvB,CAAC,CACF,CAAC;IAEF,2DAA2D;IAC3D,OAAO,CAAC,IAAI,CACV,uBAAuB,EACvB;QACE,MAAM,EAAE;YACN,IAAI,EAAE,IAAI;YACV,IAAI,EAAE,sBAAsB;SAC7B;KACF,EACD,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;QACvB,MAAM,QAAQ,GAAG,OAAO,CAAC,EAAE,IAAI,WAAW,CAAC;QAC3C,MAAM,SAAS,GAAG,MAAM,YAAY,CAAC,wBAAwB,CAAC,QAAQ,CAAC,CAAC;QAExE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;YACvB,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBAC1B,UAAU,EAAE,GAAG;gBACf,KAAK,EAAE,mBAAmB;gBAC1B,OAAO,EAAE,iHAAiH;aAC3H,CAAC,CAAC;QACL,CAAC;QAED,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC7B,MAAM,SAAS,GAAG,MAAM,cAAc,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC/D,MAAM,cAAc,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;QAE9C,OAAO,KAAK;aACT,IAAI,CAAC,GAAG,CAAC;aACT,MAAM,CAAC,cAAc,EAAE,iBAAiB,CAAC;aACzC,MAAM,CAAC,qBAAqB,EAAE,2CAA2C,CAAC;aAC1E,MAAM,CAAC,kBAAkB,EAAE,MAAM,CAAC,cAAc,CAAC,CAAC;aAClD,MAAM,CAAC,wBAAwB,EAAE,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;aAC7D,IAAI,CAAC,SAAS,CAAC,CAAC;IACrB,CAAC,CACF,CAAC;AACJ,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { FastifyPluginAsyncZod } from 'fastify-type-provider-zod';
2
+ export declare const scrapeRoutes: FastifyPluginAsyncZod;
@@ -0,0 +1,132 @@
1
+ import { ScrapeMarkdownRequestSchema, ScrapeMarkdownResponseSchema, } from '../schemas/scrape.js';
2
+ import { ErrorResponseSchema } from '../schemas/render.js';
3
+ import { scraperService } from '../services/scraper.js';
4
+ import { redisService } from '../services/redis.js';
5
+ import { env } from '../config/env.js';
6
+ export const scrapeRoutes = async (fastify) => {
7
+ // Helper to authenticate Bearer token and check monthly quota
8
+ async function authenticateAndCheckQuota(request, reply) {
9
+ const authHeader = request.headers.authorization;
10
+ let token = null;
11
+ if (authHeader && authHeader.startsWith('Bearer ')) {
12
+ token = authHeader.replace(/^Bearer\s+/i, '').trim();
13
+ }
14
+ else if (env.NODE_ENV === 'development') {
15
+ token = env.DEV_DEFAULT_API_KEY || 'sec_live_dev_test_key_12345';
16
+ }
17
+ if (!token) {
18
+ reply.code(401).send({
19
+ statusCode: 401,
20
+ error: 'Unauthorized',
21
+ message: 'Missing or invalid Bearer authentication token. Example header: Authorization: Bearer sec_live_dev_test_key_12345',
22
+ });
23
+ return null;
24
+ }
25
+ const apiKey = await redisService.getApiKey(token);
26
+ if (!apiKey) {
27
+ reply.code(401).send({
28
+ statusCode: 401,
29
+ error: 'Unauthorized',
30
+ message: 'Invalid API key provided',
31
+ });
32
+ return null;
33
+ }
34
+ if (apiKey.status !== 'active') {
35
+ reply.code(401).send({
36
+ statusCode: 401,
37
+ error: 'Unauthorized',
38
+ message: 'API key is inactive or subscription has expired',
39
+ });
40
+ return null;
41
+ }
42
+ const quota = await redisService.incrementAndCheckQuota(token, apiKey.monthly_limit);
43
+ reply.header('X-RateLimit-Limit', String(quota.limit));
44
+ reply.header('X-RateLimit-Remaining', String(quota.remaining));
45
+ if (!quota.allowed) {
46
+ reply.code(429).send({
47
+ statusCode: 429,
48
+ error: 'Too Many Requests',
49
+ message: `Monthly quota exceeded (${quota.currentUsage}/${quota.limit} operations). Please upgrade your plan.`,
50
+ });
51
+ return null;
52
+ }
53
+ return { token, apiKey, quota };
54
+ }
55
+ // Canonical POST /v1/scrape/markdown
56
+ fastify.post('/v1/scrape/markdown', {
57
+ schema: {
58
+ tags: ['Scraping & AI'],
59
+ summary: 'Scrape URL and Extract LLM-Ready Markdown',
60
+ description: 'Fetches any public URL using headless Chromium, evaluates dynamic client-side JS, strips noise/boilerplate via Mozilla Readability, and converts the clean article to structured GFM Markdown for LLMs and RAG.',
61
+ security: [{ bearerAuth: [] }],
62
+ body: ScrapeMarkdownRequestSchema,
63
+ response: {
64
+ 200: ScrapeMarkdownResponseSchema,
65
+ 400: ErrorResponseSchema,
66
+ 401: ErrorResponseSchema,
67
+ 429: ErrorResponseSchema,
68
+ 500: ErrorResponseSchema,
69
+ 504: ErrorResponseSchema,
70
+ },
71
+ },
72
+ }, async (request, reply) => {
73
+ const auth = await authenticateAndCheckQuota(request, reply);
74
+ if (!auth)
75
+ return;
76
+ const { url, strip_images } = request.body;
77
+ const cacheHash = redisService.generateScrapeCacheHash({
78
+ url,
79
+ strip_images,
80
+ });
81
+ // 1. Check Redis Cache (24h TTL)
82
+ const cached = await redisService.getCachedScrape(cacheHash);
83
+ if (cached) {
84
+ reply.header('X-Cache', 'HIT');
85
+ return reply.code(200).send({
86
+ ...cached,
87
+ cached: true,
88
+ });
89
+ }
90
+ // 2. Perform live scrape & extraction
91
+ const result = await scraperService.scrapeUrl(request.body);
92
+ // 3. Store in cache
93
+ await redisService.setCachedScrape(cacheHash, result, 86400);
94
+ reply.header('X-Cache', 'MISS');
95
+ return reply.code(200).send({
96
+ ...result,
97
+ cached: false,
98
+ });
99
+ });
100
+ // Alias POST /scrape/markdown (Hidden from OpenAPI docs)
101
+ fastify.post('/scrape/markdown', {
102
+ schema: {
103
+ hide: true,
104
+ body: ScrapeMarkdownRequestSchema,
105
+ },
106
+ }, async (request, reply) => {
107
+ const auth = await authenticateAndCheckQuota(request, reply);
108
+ if (!auth)
109
+ return;
110
+ const { url, strip_images } = request.body;
111
+ const cacheHash = redisService.generateScrapeCacheHash({
112
+ url,
113
+ strip_images,
114
+ });
115
+ const cached = await redisService.getCachedScrape(cacheHash);
116
+ if (cached) {
117
+ reply.header('X-Cache', 'HIT');
118
+ return reply.code(200).send({
119
+ ...cached,
120
+ cached: true,
121
+ });
122
+ }
123
+ const result = await scraperService.scrapeUrl(request.body);
124
+ await redisService.setCachedScrape(cacheHash, result, 86400);
125
+ reply.header('X-Cache', 'MISS');
126
+ return reply.code(200).send({
127
+ ...result,
128
+ cached: false,
129
+ });
130
+ });
131
+ };
132
+ //# sourceMappingURL=scrape.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scrape.js","sourceRoot":"","sources":["../../src/routes/scrape.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { FastifyPluginAsyncZod } from 'fastify-type-provider-zod';
2
+ export declare const templateRoutes: FastifyPluginAsyncZod;