commit 65e9032175a280768229a018c471714084792c89
parent e095fb98f5b2389794d3487c5d91003f50d2455e
Author: Amit Dutta <mail@amit.is-a.dev>
Date: Tue, 21 Jul 2026 09:03:43 +0530
Create docs.html
Diffstat:
| A | docs.html | | | 305 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
1 file changed, 305 insertions(+), 0 deletions(-)
diff --git a/docs.html b/docs.html
@@ -0,0 +1,305 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Docs · Upload Hub</title>
+
+ <link rel="preconnect" href="https://fonts.googleapis.com">
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+ <link href="https://fonts.googleapis.com/css2?family=Lexend:wght@300;400;500;600;700&display=swap" rel="stylesheet">
+ <link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,1,0" rel="stylesheet" />
+
+ <script src="https://cdn.tailwindcss.com"></script>
+ <script>
+ tailwind.config = {
+ theme: {
+ extend: {
+ fontFamily: { sans: ['Lexend', 'sans-serif'] },
+ colors: {
+ m3: {
+ background: '#FDFCFB', backgroundDark: '#1A1C19',
+ surfaceContainer: '#F1F1EA', surfaceContainerDark: '#2D2F2B',
+ surfaceContainerHigh: '#EBEBE4', surfaceContainerHighDark: '#333531',
+ surfaceContainerHighest: '#E5E6DE', surfaceContainerHighestDark: '#434541',
+ onSurface: '#1A1C19', onSurfaceDark: '#E3E3DC',
+ onSurfaceVariant: '#43483E', onSurfaceVariantDark: '#C3C8BC',
+ primary: '#386A20', primaryDark: '#9CD67D',
+ onPrimary: '#FFFFFF', onPrimaryDark: '#0C3900',
+ secondaryContainer: '#D7E8CD', secondaryContainerDark: '#374B2E',
+ onSecondaryContainer: '#111F0F', onSecondaryContainerDark: '#D7E8CD',
+ outline: '#74796D', outlineDark: '#8D9286',
+ }
+ }
+ }
+ }
+ }
+ </script>
+
+ <style>
+ body { background-color: #FDFCFB; margin: 0; -webkit-font-smoothing: antialiased; }
+ @media (prefers-color-scheme: dark) { body { background-color: #1A1C19; } }
+ .diagram-box { background: #F1F1EA; }
+ @media (prefers-color-scheme: dark) { .diagram-box { background: #2D2F2B; } }
+ .node { fill: #D7E8CD; stroke: #386A20; }
+ @media (prefers-color-scheme: dark) { .node { fill: #374B2E; stroke: #9CD67D; } }
+ .node-alt { fill: #E5E6DE; stroke: #74796D; }
+ @media (prefers-color-scheme: dark) { .node-alt { fill: #434541; stroke: #8D9286; } }
+ .node-text { fill: #111F0F; font-family: 'Lexend', sans-serif; font-size: 12px; font-weight: 600; }
+ @media (prefers-color-scheme: dark) { .node-text { fill: #D7E8CD; } }
+ .node-text-alt { fill: #1A1C19; font-family: 'Lexend', sans-serif; font-size: 12px; font-weight: 500; }
+ @media (prefers-color-scheme: dark) { .node-text-alt { fill: #E3E3DC; } }
+ .arrow { stroke: #74796D; stroke-width: 2; fill: none; marker-end: url(#arrowhead); }
+ @media (prefers-color-scheme: dark) { .arrow { stroke: #8D9286; } }
+ .arrow-label { fill: #43483E; font-family: 'Lexend', sans-serif; font-size: 10px; }
+ @media (prefers-color-scheme: dark) { .arrow-label { fill: #C3C8BC; } }
+ code { background: #E5E6DE; padding: 2px 6px; border-radius: 4px; font-size: 0.85em; }
+ @media (prefers-color-scheme: dark) { code { background: #434541; } }
+ </style>
+</head>
+<body class="bg-m3-background dark:bg-m3-backgroundDark text-m3-onSurface dark:text-m3-onSurfaceDark font-sans">
+
+<div class="max-w-[720px] mx-auto px-4 py-10">
+
+ <a href="/" class="inline-flex items-center gap-1 text-sm font-semibold text-m3-primary dark:text-m3-primaryDark mb-6">
+ <span class="material-symbols-outlined text-[18px]">arrow_back</span> Back to Upload
+ </a>
+
+ <h1 class="text-3xl font-bold tracking-tight mb-2">How this tool works</h1>
+ <p class="text-m3-onSurfaceVariant dark:text-m3-onSurfaceVariantDark mb-10">
+ A permanent, free file host built on a Hugging Face dataset repo, served through a serverless
+ FastAPI backend on Vercel. Here's exactly what happens behind each button.
+ </p>
+
+ <!-- ===================== Section: Architecture ===================== -->
+ <section class="mb-14">
+ <h2 class="text-xl font-bold mb-3">1. The building blocks</h2>
+ <p class="text-sm leading-relaxed text-m3-onSurfaceVariant dark:text-m3-onSurfaceVariantDark mb-4">
+ There are three pieces: the <strong>frontend</strong> (this website, plain HTML/JS), the
+ <strong>backend</strong> (a Python FastAPI app running as a Vercel serverless function), and
+ <strong>storage</strong> (a public Hugging Face dataset repo, which gives ~unlimited free storage
+ and a permanent, directly-linkable URL per file).
+ </p>
+ <div class="diagram-box rounded-[20px] p-4 overflow-x-auto">
+ <svg viewBox="0 0 680 140" xmlns="http://www.w3.org/2000/svg" class="min-w-[600px]">
+ <defs>
+ <marker id="arrowhead" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto">
+ <path d="M0,0 L8,4 L0,8 Z" class="node-alt" />
+ </marker>
+ </defs>
+ <rect x="20" y="45" width="150" height="50" rx="12" class="node-alt"/>
+ <text x="95" y="65" text-anchor="middle" class="node-text-alt">Browser</text>
+ <text x="95" y="80" text-anchor="middle" class="node-text-alt" font-size="10">index.html</text>
+
+ <rect x="265" y="45" width="150" height="50" rx="12" class="node"/>
+ <text x="340" y="65" text-anchor="middle" class="node-text">FastAPI backend</text>
+ <text x="340" y="80" text-anchor="middle" class="node-text" font-size="10">index.py (Vercel)</text>
+
+ <rect x="510" y="45" width="150" height="50" rx="12" class="node-alt"/>
+ <text x="585" y="65" text-anchor="middle" class="node-text-alt">HF Dataset</text>
+ <text x="585" y="80" text-anchor="middle" class="node-text-alt" font-size="10">notamitgamer/uploads</text>
+
+ <path d="M170,65 L265,65" class="arrow"/>
+ <text x="217" y="55" text-anchor="middle" class="arrow-label">HTTP request</text>
+ <path d="M415,65 L510,65" class="arrow"/>
+ <text x="462" y="55" text-anchor="middle" class="arrow-label">HF API</text>
+ </svg>
+ </div>
+ </section>
+
+ <!-- ===================== Section: Regular upload ===================== -->
+ <section class="mb-14">
+ <h2 class="text-xl font-bold mb-3">2. Uploading a file</h2>
+ <p class="text-sm leading-relaxed text-m3-onSurfaceVariant dark:text-m3-onSurfaceVariantDark mb-4">
+ Drop a file (or several, or paste one) and the browser sends it straight to <code>/api/upload</code>
+ as <code>multipart/form-data</code>. No file ever touches disk on the server — it's read into memory
+ and streamed to Hugging Face.
+ </p>
+ <div class="diagram-box rounded-[20px] p-4 overflow-x-auto mb-4">
+ <svg viewBox="0 0 680 320" xmlns="http://www.w3.org/2000/svg" class="min-w-[600px]">
+ <rect x="20" y="10" width="200" height="40" rx="10" class="node-alt"/>
+ <text x="120" y="35" text-anchor="middle" class="node-text-alt">Select / drop / paste file(s)</text>
+
+ <path d="M120,50 L120,80" class="arrow"/>
+ <rect x="20" y="80" width="200" height="40" rx="10" class="node-alt"/>
+ <text x="120" y="105" text-anchor="middle" class="node-text-alt">POST /api/upload</text>
+
+ <path d="M120,120 L120,150" class="arrow"/>
+ <rect x="20" y="150" width="200" height="50" rx="10" class="node"/>
+ <text x="120" y="170" text-anchor="middle" class="node-text">Sanitize custom_id</text>
+ <text x="120" y="184" text-anchor="middle" class="node-text" font-size="10">or generate random ID</text>
+
+ <path d="M120,200 L120,230" class="arrow"/>
+ <rect x="20" y="230" width="200" height="40" rx="10" class="node"/>
+ <text x="120" y="255" text-anchor="middle" class="node-text">Upload bytes to HF dataset</text>
+
+ <path d="M220,250 L340,250" class="arrow"/>
+ <rect x="340" y="230" width="200" height="60" rx="10" class="node"/>
+ <text x="440" y="252" text-anchor="middle" class="node-text">Expiry chosen?</text>
+ <text x="440" y="268" text-anchor="middle" class="node-text" font-size="10">write item.meta.json</text>
+ <text x="440" y="280" text-anchor="middle" class="node-text" font-size="10">with expires_at</text>
+
+ <path d="M440,290 L440,310 L120,310 L120,300" class="arrow"/>
+ <text x="280" y="305" text-anchor="middle" class="arrow-label">return link to browser</text>
+ </svg>
+ </div>
+ <ul class="text-sm leading-relaxed text-m3-onSurfaceVariant dark:text-m3-onSurfaceVariantDark list-disc pl-5 space-y-1">
+ <li>Any file extension is accepted — the server never inspects file type, only reuses the extension for the generated ID.</li>
+ <li>Max size is <strong>~4.4 MB</strong> per file, kept just under Vercel's hard 4.5 MB request-body limit.</li>
+ <li>A custom ID is only allowed when uploading exactly one file, and is sanitized to <code>[a-zA-Z0-9._-]</code> to block path traversal.</li>
+ </ul>
+ </section>
+
+ <!-- ===================== Section: Link types ===================== -->
+ <section class="mb-14">
+ <h2 class="text-xl font-bold mb-3">3. Permanent link vs. expiring link</h2>
+ <p class="text-sm leading-relaxed text-m3-onSurfaceVariant dark:text-m3-onSurfaceVariantDark mb-4">
+ The link you get back depends on whether you picked an expiry. This matters because it changes
+ how the link is served when someone opens it later.
+ </p>
+ <div class="diagram-box rounded-[20px] p-4 overflow-x-auto mb-4">
+ <svg viewBox="0 0 680 260" xmlns="http://www.w3.org/2000/svg" class="min-w-[600px]">
+ <rect x="250" y="10" width="180" height="40" rx="10" class="node-alt"/>
+ <text x="340" y="35" text-anchor="middle" class="node-text-alt">Someone opens the link</text>
+
+ <path d="M310,50 L150,90" class="arrow"/>
+ <text x="200" y="70" text-anchor="middle" class="arrow-label">no expiry set</text>
+ <path d="M370,50 L530,90" class="arrow"/>
+ <text x="480" y="70" text-anchor="middle" class="arrow-label">expiry set</text>
+
+ <rect x="30" y="90" width="240" height="40" rx="10" class="node"/>
+ <text x="150" y="115" text-anchor="middle" class="node-text">/<item_id> — static redirect</text>
+
+ <path d="M150,130 L150,160" class="arrow"/>
+ <rect x="30" y="160" width="240" height="50" rx="10" class="node"/>
+ <text x="150" y="180" text-anchor="middle" class="node-text">vercel.json redirect rule</text>
+ <text x="150" y="195" text-anchor="middle" class="node-text" font-size="10">instant, no function cold-start</text>
+
+ <rect x="410" y="90" width="240" height="40" rx="10" class="node"/>
+ <text x="530" y="115" text-anchor="middle" class="node-text">/api/file/<item_id></text>
+
+ <path d="M530,130 L530,160" class="arrow"/>
+ <rect x="410" y="160" width="240" height="60" rx="10" class="node"/>
+ <text x="530" y="180" text-anchor="middle" class="node-text">Backend checks item.meta.json</text>
+ <text x="530" y="196" text-anchor="middle" class="node-text" font-size="10">expired? → HTTP 410</text>
+ <text x="530" y="210" text-anchor="middle" class="node-text" font-size="10">valid? → 302 redirect to HF</text>
+
+ <path d="M150,210 L150,230 L340,230" class="arrow"/>
+ <path d="M530,220 L530,230 L340,230" class="arrow"/>
+ <rect x="240" y="230" width="200" height="30" rx="8" class="node-alt"/>
+ <text x="340" y="250" text-anchor="middle" class="node-text-alt">File served from Hugging Face</text>
+ </svg>
+ </div>
+ <ul class="text-sm leading-relaxed text-m3-onSurfaceVariant dark:text-m3-onSurfaceVariantDark list-disc pl-5 space-y-1">
+ <li><strong>Permanent links</strong> are handled entirely by Vercel's static redirect rules — the Python function never even runs, so they're as fast as a link can be.</li>
+ <li><strong>Expiring links</strong> must go through the backend on every open, since something has to check "has this passed its expiry time yet?" before deciding where to send you.</li>
+ <li>Nothing is ever deleted. An expired link just stops redirecting — the file stays in the dataset.</li>
+ </ul>
+ </section>
+
+ <!-- ===================== Section: Upload via URL ===================== -->
+ <section class="mb-14">
+ <h2 class="text-xl font-bold mb-3">4. Uploading from a URL</h2>
+ <p class="text-sm leading-relaxed text-m3-onSurfaceVariant dark:text-m3-onSurfaceVariantDark mb-4">
+ Instead of uploading from your device, you can hand the tool a link and have the server fetch
+ and re-host it — useful for re-hosting an image or file that's only temporarily available elsewhere.
+ </p>
+ <div class="diagram-box rounded-[20px] p-4 overflow-x-auto">
+ <svg viewBox="0 0 680 100" xmlns="http://www.w3.org/2000/svg" class="min-w-[600px]">
+ <rect x="10" y="30" width="150" height="40" rx="10" class="node-alt"/>
+ <text x="85" y="55" text-anchor="middle" class="node-text-alt">You paste a URL</text>
+
+ <path d="M160,50 L250,50" class="arrow"/>
+ <rect x="250" y="30" width="180" height="40" rx="10" class="node"/>
+ <text x="340" y="55" text-anchor="middle" class="node-text">Backend fetches the URL</text>
+
+ <path d="M430,50 L520,50" class="arrow"/>
+ <rect x="520" y="30" width="150" height="40" rx="10" class="node-alt"/>
+ <text x="595" y="55" text-anchor="middle" class="node-text-alt">Uploaded to HF, same as normal</text>
+ </svg>
+ </div>
+ <p class="text-sm leading-relaxed text-m3-onSurfaceVariant dark:text-m3-onSurfaceVariantDark mt-3">
+ Same 4.4 MB size limit applies to the fetched file, and it supports the same custom ID and expiry options.
+ </p>
+ </section>
+
+ <!-- ===================== Section: Batch links ===================== -->
+ <section class="mb-14">
+ <h2 class="text-xl font-bold mb-3">5. Batch links</h2>
+ <p class="text-sm leading-relaxed text-m3-onSurfaceVariant dark:text-m3-onSurfaceVariantDark mb-4">
+ Upload more than one file at once and, alongside each file's own link, you also get a single
+ shareable <code>/batch/<id></code> link that opens a page listing every file from that batch.
+ </p>
+ <div class="diagram-box rounded-[20px] p-4 overflow-x-auto">
+ <svg viewBox="0 0 680 180" xmlns="http://www.w3.org/2000/svg" class="min-w-[600px]">
+ <rect x="20" y="20" width="200" height="40" rx="10" class="node-alt"/>
+ <text x="120" y="45" text-anchor="middle" class="node-text-alt">Upload 3+ files together</text>
+
+ <path d="M120,60 L120,90" class="arrow"/>
+ <rect x="20" y="90" width="200" height="50" rx="10" class="node"/>
+ <text x="120" y="110" text-anchor="middle" class="node-text">Each file uploaded normally</text>
+ <text x="120" y="125" text-anchor="middle" class="node-text" font-size="10">gets its own item_id</text>
+
+ <path d="M220,115 L340,115" class="arrow"/>
+ <rect x="340" y="90" width="200" height="50" rx="10" class="node"/>
+ <text x="440" y="110" text-anchor="middle" class="node-text">Manifest written</text>
+ <text x="440" y="125" text-anchor="middle" class="node-text" font-size="10">_batches/<batch_id>.json</text>
+
+ <path d="M540,115 L620,115 L620,150" class="arrow"/>
+ <rect x="510" y="150" width="150" height="30" rx="8" class="node-alt"/>
+ <text x="585" y="170" text-anchor="middle" class="node-text-alt" font-size="10">/batch/<id> page</text>
+ </svg>
+ </div>
+ </section>
+
+ <!-- ===================== Section: Limits table ===================== -->
+ <section class="mb-14">
+ <h2 class="text-xl font-bold mb-3">6. Limits & behavior at a glance</h2>
+ <div class="bg-m3-surfaceContainer dark:bg-m3-surfaceContainerDark rounded-[16px] overflow-hidden text-sm">
+ <table class="w-full border-collapse">
+ <tbody>
+ <tr class="border-b border-m3-surfaceContainerHighest dark:border-m3-surfaceContainerHighestDark">
+ <td class="px-4 py-3 font-semibold w-1/2">Max file size</td>
+ <td class="px-4 py-3 text-m3-onSurfaceVariant dark:text-m3-onSurfaceVariantDark">~4.4 MB per file (Vercel request-body limit)</td>
+ </tr>
+ <tr class="border-b border-m3-surfaceContainerHighest dark:border-m3-surfaceContainerHighestDark">
+ <td class="px-4 py-3 font-semibold">File types</td>
+ <td class="px-4 py-3 text-m3-onSurfaceVariant dark:text-m3-onSurfaceVariantDark">Any extension, unrestricted</td>
+ </tr>
+ <tr class="border-b border-m3-surfaceContainerHighest dark:border-m3-surfaceContainerHighestDark">
+ <td class="px-4 py-3 font-semibold">Multiple files</td>
+ <td class="px-4 py-3 text-m3-onSurfaceVariant dark:text-m3-onSurfaceVariantDark">Supported; each gets its own link + one shared batch link</td>
+ </tr>
+ <tr class="border-b border-m3-surfaceContainerHighest dark:border-m3-surfaceContainerHighestDark">
+ <td class="px-4 py-3 font-semibold">Custom ID</td>
+ <td class="px-4 py-3 text-m3-onSurfaceVariant dark:text-m3-onSurfaceVariantDark">Single-file uploads only, sanitized to letters/numbers/dot/dash/underscore</td>
+ </tr>
+ <tr class="border-b border-m3-surfaceContainerHighest dark:border-m3-surfaceContainerHighestDark">
+ <td class="px-4 py-3 font-semibold">Expiry</td>
+ <td class="px-4 py-3 text-m3-onSurfaceVariant dark:text-m3-onSurfaceVariantDark">Optional: 1h / 1d / 7d / 30d / never. Disables the link only — the file itself is never deleted</td>
+ </tr>
+ <tr class="border-b border-m3-surfaceContainerHighest dark:border-m3-surfaceContainerHighestDark">
+ <td class="px-4 py-3 font-semibold">Deletion</td>
+ <td class="px-4 py-3 text-m3-onSurfaceVariant dark:text-m3-onSurfaceVariantDark">Not supported by design</td>
+ </tr>
+ <tr class="border-b border-m3-surfaceContainerHighest dark:border-m3-surfaceContainerHighestDark">
+ <td class="px-4 py-3 font-semibold">Authentication</td>
+ <td class="px-4 py-3 text-m3-onSurfaceVariant dark:text-m3-onSurfaceVariantDark">None — the tool is intentionally open</td>
+ </tr>
+ <tr>
+ <td class="px-4 py-3 font-semibold">Storage</td>
+ <td class="px-4 py-3 text-m3-onSurfaceVariant dark:text-m3-onSurfaceVariantDark">Public Hugging Face dataset repo</td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ </section>
+
+ <a href="/" class="inline-flex items-center gap-1 text-sm font-semibold text-m3-primary dark:text-m3-primaryDark">
+ <span class="material-symbols-outlined text-[18px]">arrow_back</span> Back to Upload
+ </a>
+
+</div>
+
+</body>
+</html>