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.
- package/LICENSE +21 -0
- package/README.md +160 -2
- package/dist/config/env.d.ts +77 -0
- package/dist/config/env.js +38 -0
- package/dist/config/env.js.map +1 -0
- package/dist/config/plans.d.ts +15 -0
- package/dist/config/plans.js +104 -0
- package/dist/config/plans.js.map +1 -0
- package/dist/config/templates.d.ts +10 -0
- package/dist/config/templates.js +746 -0
- package/dist/config/templates.js.map +1 -0
- package/dist/mcp/server.d.ts +10 -0
- package/dist/mcp/server.js +897 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/stdio.d.ts +2 -0
- package/dist/mcp/stdio.js +27 -0
- package/dist/mcp/stdio.js.map +1 -0
- package/dist/routes/dashboard.d.ts +2 -0
- package/dist/routes/dashboard.js +454 -0
- package/dist/routes/dashboard.js.map +1 -0
- package/dist/routes/mcp.d.ts +2 -0
- package/dist/routes/mcp.js +329 -0
- package/dist/routes/mcp.js.map +1 -0
- package/dist/routes/render.d.ts +2 -0
- package/dist/routes/render.js +444 -0
- package/dist/routes/render.js.map +1 -0
- package/dist/routes/scrape.d.ts +2 -0
- package/dist/routes/scrape.js +132 -0
- package/dist/routes/scrape.js.map +1 -0
- package/dist/routes/templates.d.ts +2 -0
- package/dist/routes/templates.js +2449 -0
- package/dist/routes/templates.js.map +1 -0
- package/dist/routes/usage.d.ts +31 -0
- package/dist/routes/usage.js +73 -0
- package/dist/routes/usage.js.map +1 -0
- package/dist/routes/webhooks.d.ts +2 -0
- package/dist/routes/webhooks.js +227 -0
- package/dist/routes/webhooks.js.map +1 -0
- package/dist/schemas/render.d.ts +422 -0
- package/dist/schemas/render.js +91 -0
- package/dist/schemas/render.js.map +1 -0
- package/dist/schemas/scrape.d.ts +65 -0
- package/dist/schemas/scrape.js +24 -0
- package/dist/schemas/scrape.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +206 -0
- package/dist/server.js.map +1 -0
- package/dist/services/browser.d.ts +21 -0
- package/dist/services/browser.js +299 -0
- package/dist/services/browser.js.map +1 -0
- package/dist/services/carousel.d.ts +43 -0
- package/dist/services/carousel.js +300 -0
- package/dist/services/carousel.js.map +1 -0
- package/dist/services/chart.d.ts +29 -0
- package/dist/services/chart.js +129 -0
- package/dist/services/chart.js.map +1 -0
- package/dist/services/eInvoice.d.ts +62 -0
- package/dist/services/eInvoice.js +413 -0
- package/dist/services/eInvoice.js.map +1 -0
- package/dist/services/email.d.ts +24 -0
- package/dist/services/email.js +177 -0
- package/dist/services/email.js.map +1 -0
- package/dist/services/mockup.d.ts +14 -0
- package/dist/services/mockup.js +130 -0
- package/dist/services/mockup.js.map +1 -0
- package/dist/services/pdfStamper.d.ts +16 -0
- package/dist/services/pdfStamper.js +47 -0
- package/dist/services/pdfStamper.js.map +1 -0
- package/dist/services/qr.d.ts +20 -0
- package/dist/services/qr.js +44 -0
- package/dist/services/qr.js.map +1 -0
- package/dist/services/redis.d.ts +65 -0
- package/dist/services/redis.js +318 -0
- package/dist/services/redis.js.map +1 -0
- package/dist/services/scraper.d.ts +16 -0
- package/dist/services/scraper.js +138 -0
- package/dist/services/scraper.js.map +1 -0
- package/dist/services/storage.d.ts +38 -0
- package/dist/services/storage.js +123 -0
- package/dist/services/storage.js.map +1 -0
- package/dist/services/stripe.d.ts +32 -0
- package/dist/services/stripe.js +229 -0
- package/dist/services/stripe.js.map +1 -0
- package/dist/services/visualDiff.d.ts +33 -0
- package/dist/services/visualDiff.js +218 -0
- package/dist/services/visualDiff.js.map +1 -0
- 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":"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"}
|
|
@@ -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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"}
|