diff --git "a/\001@" "b/\001@" new file mode 100644 index 0000000..e69de29 diff --git a/docker-compose.yml b/docker-compose.yml index 1792349..6b3c4c5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,8 +18,8 @@ services: - SMTP_HOST=host.docker.internal - SMTP_PORT=25 - POOL_SIZE=15 - - BROWSER_COUNT=2 - - PAGES_PER_BROWSER=8 + - BROWSER_COUNT=1 + - PAGES_PER_BROWSER=15 volumes: - docfast-data:/app/data mem_limit: 2560m diff --git a/package-lock.json b/package-lock.json index 1993739..1a0073c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,8 @@ "nanoid": "^5.0.0", "nodemailer": "^8.0.1", "puppeteer": "^24.0.0", - "stripe": "^20.3.1" + "stripe": "^20.3.1", + "swagger-ui-dist": "^5.31.0" }, "devDependencies": { "@types/express": "^5.0.0", @@ -892,6 +893,12 @@ "win32" ] }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true + }, "node_modules/@tootallnate/quickjs-emscripten": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", @@ -3414,6 +3421,14 @@ } } }, + "node_modules/swagger-ui-dist": { + "version": "5.31.0", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.31.0.tgz", + "integrity": "sha512-zSUTIck02fSga6rc0RZP3b7J7wgHXwLea8ZjgLA3Vgnb8QeOl3Wou2/j5QkzSGeoz6HusP/coYuJl33aQxQZpg==", + "dependencies": { + "@scarf/scarf": "=1.4.0" + } + }, "node_modules/tar-fs": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz", diff --git a/package.json b/package.json index 6bb2ff8..3cc82fc 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,8 @@ "nanoid": "^5.0.0", "nodemailer": "^8.0.1", "puppeteer": "^24.0.0", - "stripe": "^20.3.1" + "stripe": "^20.3.1", + "swagger-ui-dist": "^5.31.0" }, "devDependencies": { "@types/express": "^5.0.0", diff --git a/public/app.js b/public/app.js index 8643052..9841160 100644 --- a/public/app.js +++ b/public/app.js @@ -298,3 +298,143 @@ document.addEventListener('DOMContentLoaded', function() { }); }); }); + +// --- Email Change --- +var emailChangeApiKey = ''; +var emailChangeNewEmail = ''; + +function showEmailChangeState(state) { + ['emailChangeInitial', 'emailChangeLoading', 'emailChangeVerify', 'emailChangeResult'].forEach(function(id) { + var el = document.getElementById(id); + if (el) el.classList.remove('active'); + }); + document.getElementById(state).classList.add('active'); +} + +function openEmailChange() { + closeSignup(); + closeRecover(); + document.getElementById('emailChangeModal').classList.add('active'); + showEmailChangeState('emailChangeInitial'); + var errEl = document.getElementById('emailChangeError'); + if (errEl) errEl.style.display = 'none'; + var verifyErrEl = document.getElementById('emailChangeVerifyError'); + if (verifyErrEl) verifyErrEl.style.display = 'none'; + document.getElementById('emailChangeApiKey').value = ''; + document.getElementById('emailChangeNewEmail').value = ''; + document.getElementById('emailChangeCode').value = ''; + emailChangeApiKey = ''; + emailChangeNewEmail = ''; +} + +function closeEmailChange() { + document.getElementById('emailChangeModal').classList.remove('active'); +} + +async function submitEmailChange() { + var errEl = document.getElementById('emailChangeError'); + var btn = document.getElementById('emailChangeBtn'); + var apiKey = document.getElementById('emailChangeApiKey').value.trim(); + var newEmail = document.getElementById('emailChangeNewEmail').value.trim(); + + if (!apiKey) { + errEl.textContent = 'Please enter your API key.'; + errEl.style.display = 'block'; + return; + } + if (!newEmail || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(newEmail)) { + errEl.textContent = 'Please enter a valid email address.'; + errEl.style.display = 'block'; + return; + } + + errEl.style.display = 'none'; + btn.disabled = true; + showEmailChangeState('emailChangeLoading'); + + try { + var res = await fetch('/v1/email-change', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ apiKey: apiKey, newEmail: newEmail }) + }); + var data = await res.json(); + + if (!res.ok) { + showEmailChangeState('emailChangeInitial'); + errEl.textContent = data.error || 'Something went wrong.'; + errEl.style.display = 'block'; + btn.disabled = false; + return; + } + + emailChangeApiKey = apiKey; + emailChangeNewEmail = newEmail; + document.getElementById('emailChangeEmailDisplay').textContent = newEmail; + showEmailChangeState('emailChangeVerify'); + document.getElementById('emailChangeCode').focus(); + btn.disabled = false; + } catch (err) { + showEmailChangeState('emailChangeInitial'); + errEl.textContent = 'Network error. Please try again.'; + errEl.style.display = 'block'; + btn.disabled = false; + } +} + +async function submitEmailChangeVerify() { + var errEl = document.getElementById('emailChangeVerifyError'); + var btn = document.getElementById('emailChangeVerifyBtn'); + var code = document.getElementById('emailChangeCode').value.trim(); + + if (!code || !/^\d{6}$/.test(code)) { + errEl.textContent = 'Please enter a 6-digit code.'; + errEl.style.display = 'block'; + return; + } + + errEl.style.display = 'none'; + btn.disabled = true; + + try { + var res = await fetch('/v1/email-change/verify', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ apiKey: emailChangeApiKey, newEmail: emailChangeNewEmail, code: code }) + }); + var data = await res.json(); + + if (!res.ok) { + errEl.textContent = data.error || 'Verification failed.'; + errEl.style.display = 'block'; + btn.disabled = false; + return; + } + + document.getElementById('emailChangeNewDisplay').textContent = data.newEmail || emailChangeNewEmail; + showEmailChangeState('emailChangeResult'); + } catch (err) { + errEl.textContent = 'Network error. Please try again.'; + errEl.style.display = 'block'; + btn.disabled = false; + } +} + +// Add event listeners for email change (append to DOMContentLoaded) +document.addEventListener('DOMContentLoaded', function() { + var closeBtn = document.getElementById('btn-close-email-change'); + if (closeBtn) closeBtn.addEventListener('click', closeEmailChange); + + var changeBtn = document.getElementById('emailChangeBtn'); + if (changeBtn) changeBtn.addEventListener('click', submitEmailChange); + + var verifyBtn = document.getElementById('emailChangeVerifyBtn'); + if (verifyBtn) verifyBtn.addEventListener('click', submitEmailChangeVerify); + + var modal = document.getElementById('emailChangeModal'); + if (modal) modal.addEventListener('click', function(e) { if (e.target === this) closeEmailChange(); }); + + document.querySelectorAll('.open-email-change').forEach(function(el) { + el.addEventListener('click', function(e) { e.preventDefault(); openEmailChange(); }); + }); +}); diff --git a/public/docs.html b/public/docs.html index 8ec3556..26d0a6a 100644 --- a/public/docs.html +++ b/public/docs.html @@ -4,393 +4,125 @@
Convert HTML, Markdown, and URLs to PDF. Built-in invoice & receipt templates.
-All conversion and template endpoints require an API key. Pass it using either method:
-Authorization: Bearer df_free_your_api_key_here-
Or use the X-API-Key header:
X-API-Key: df_free_your_api_key_here-
Get a free API key instantly — no credit card required:
-curl -X POST https://docfast.dev/v1/signup/free \
- -H "Content-Type: application/json" \
- -d '{"email": "you@example.com"}'
- Convert raw HTML (with optional CSS) to a PDF document.
- -| Field | Type | Description |
|---|---|---|
html required | string | HTML content to convert |
css | string | Additional CSS to inject |
format | string | Page size: A4 (default), Letter, Legal, A3 |
landscape | boolean | Landscape orientation (default: false) |
margin | object | {top, right, bottom, left} in CSS units (default: 20mm each) |
curl -X POST https://docfast.dev/v1/convert/html \
- -H "Authorization: Bearer YOUR_KEY" \
- -H "Content-Type: application/json" \
- -d '{
- "html": "<h1>Hello World</h1><p>Generated by DocFast.</p>",
- "css": "h1 { color: navy; }",
- "format": "A4"
- }' \
- -o output.pdf
-
- 200 OK — Returns the PDF as application/pdf binary stream.
html field
- 401 Invalid/missing API key
- 429 Rate limited
- Convert Markdown to a styled PDF with syntax highlighting for code blocks.
- -| Field | Type | Description |
|---|---|---|
markdown required | string | Markdown content |
css | string | Additional CSS to inject |
format | string | Page size (default: A4) |
landscape | boolean | Landscape orientation |
margin | object | Custom margins |
curl -X POST https://docfast.dev/v1/convert/markdown \
- -H "Authorization: Bearer YOUR_KEY" \
- -H "Content-Type: application/json" \
- -d '{
- "markdown": "# Monthly Report\n\n## Summary\n\nRevenue increased by **15%** this quarter.\n\n| Metric | Value |\n|--------|-------|\n| Users | 1,234 |\n| MRR | $5,670 |"
- }' \
- -o report.pdf
-
- 200 OK — Returns application/pdf.
markdown field
- 401 Invalid/missing API key
- Navigate to a URL and convert the rendered page to PDF. Supports JavaScript-rendered pages.
- -| Field | Type | Description |
|---|---|---|
url required | string | URL to convert (must start with http:// or https://) |
waitUntil | string | load (default), domcontentloaded, networkidle0, networkidle2 |
format | string | Page size (default: A4) |
landscape | boolean | Landscape orientation |
margin | object | Custom margins |
curl -X POST https://docfast.dev/v1/convert/url \
- -H "Authorization: Bearer YOUR_KEY" \
- -H "Content-Type: application/json" \
- -d '{
- "url": "https://example.com",
- "waitUntil": "networkidle0",
- "format": "Letter"
- }' \
- -o page.pdf
-
- 200 OK — Returns application/pdf.
List all available document templates with their field definitions.
- -curl https://docfast.dev/v1/templates \ - -H "Authorization: Bearer YOUR_KEY"- -
{
- "templates": [
- {
- "id": "invoice",
- "name": "Invoice",
- "description": "Professional invoice with line items, taxes, and payment details",
- "fields": [
- {"name": "invoiceNumber", "type": "string", "required": true},
- {"name": "date", "type": "string", "required": true},
- {"name": "from", "type": "object", "required": true, "description": "Sender: {name, address?, email?, phone?, vatId?}"},
- {"name": "to", "type": "object", "required": true, "description": "Recipient: {name, address?, email?, vatId?}"},
- {"name": "items", "type": "array", "required": true, "description": "Line items: [{description, quantity, unitPrice, taxRate?}]"},
- {"name": "currency", "type": "string", "required": false},
- {"name": "notes", "type": "string", "required": false},
- {"name": "paymentDetails", "type": "string", "required": false}
- ]
- },
- {
- "id": "receipt",
- "name": "Receipt",
- "description": "Simple receipt for payments received",
- "fields": [ ... ]
- }
- ]
-}
- Render a template with your data and get a PDF. No HTML needed — just pass structured data.
- -| Param | Description |
|---|---|
:id | Template ID (invoice or receipt) |
| Field | Type | Description |
|---|---|---|
data required | object | Template data (see field definitions from /v1/templates) |
curl -X POST https://docfast.dev/v1/templates/invoice/render \
- -H "Authorization: Bearer YOUR_KEY" \
- -H "Content-Type: application/json" \
- -d '{
- "data": {
- "invoiceNumber": "INV-2026-001",
- "date": "2026-02-14",
- "dueDate": "2026-03-14",
- "from": {
- "name": "Acme Corp",
- "address": "123 Main St, Vienna",
- "email": "billing@acme.com",
- "vatId": "ATU12345678"
- },
- "to": {
- "name": "Client Inc",
- "address": "456 Oak Ave, Berlin",
- "email": "accounts@client.com"
- },
- "items": [
- {"description": "Web Development", "quantity": 40, "unitPrice": 95, "taxRate": 20},
- {"description": "Hosting (monthly)", "quantity": 1, "unitPrice": 29}
+ ← Back to docfast.dev
+
+
+