uploads

Log | Files | Refs

commit 50b0789ab73ad286a70d56fb048812fda6520b4b
parent b26232b4553a7c3c9b11abf8e84ee1a4acdf8da8
Author: Amit Dutta <mail@amit.is-a.dev>
Date:   Tue, 21 Jul 2026 09:52:28 +0530

Refactor upload logic to remove expiry handling

Removed expiry handling from upload functions and related metadata. Updated upload logic to simplify file handling and link generation.
Diffstat:
Mapi/index.py | 116+++++++++++++++----------------------------------------------------------------
1 file changed, 22 insertions(+), 94 deletions(-)

diff --git a/api/index.py b/api/index.py @@ -3,12 +3,12 @@ import re import json import random import string -from datetime import datetime, timedelta, timezone +from datetime import datetime, timezone from typing import List, Optional import httpx from fastapi import FastAPI, File, UploadFile, Form, HTTPException -from fastapi.responses import RedirectResponse, JSONResponse, HTMLResponse +from fastapi.responses import HTMLResponse from huggingface_hub import HfApi, hf_hub_download from huggingface_hub.utils import EntryNotFoundError @@ -28,15 +28,8 @@ MAX_UPLOAD_BYTES = int(4.4 * 1024 * 1024) # stay under Vercel's 4.5MB body limi _SAFE_ID_RE = re.compile(r"[^a-zA-Z0-9._-]+") - def sanitize_id(raw: str, ext: str) -> str: - """Turn a user-supplied custom_id into a safe repo path segment. - - - strips anything that isn't alnum, dot, dash, underscore - - removes leading dots/dashes (no path traversal, no dotfiles) - - enforces a sane max length - - re-appends the original extension if missing - """ + """Turn a user-supplied custom_id into a safe repo path segment.""" raw = raw.strip() raw = raw.replace("/", "-").replace("\\", "-") raw = _SAFE_ID_RE.sub("", raw) @@ -48,25 +41,21 @@ def sanitize_id(raw: str, ext: str) -> str: raw += ext return raw - def random_id(ext: str) -> str: timestamp = datetime.now().strftime("%Y%m%d-%H%M%S") random_str = "".join(random.choices(string.ascii_letters + string.digits, k=6)) return f"{timestamp}-{random_str}{ext}" - def random_batch_id() -> str: timestamp = datetime.now().strftime("%Y%m%d-%H%M%S") random_str = "".join(random.choices(string.ascii_letters + string.digits, k=8)) return f"{timestamp}-{random_str}" - def get_api() -> HfApi: if not HF_TOKEN: raise HTTPException(status_code=500, detail="HF_TOKEN environment variable is not set") return HfApi() - def ensure_repo(api: HfApi): api.create_repo( repo_id=DATASET_REPO, @@ -76,7 +65,6 @@ def ensure_repo(api: HfApi): private=False, ) - def upload_bytes(api: HfApi, content: bytes, path_in_repo: str): api.upload_file( path_or_fileobj=content, @@ -86,48 +74,30 @@ def upload_bytes(api: HfApi, content: bytes, path_in_repo: str): token=HF_TOKEN, ) - -def write_meta(api: HfApi, item_id: str, expires_at: Optional[str]): - if not expires_at: - return - meta = {"expires_at": expires_at} - upload_bytes(api, json.dumps(meta).encode(), f"{item_id}.meta.json") - - -def raw_url(item_id: str) -> str: - return f"https://huggingface.co/datasets/{DATASET_REPO}/resolve/main/{item_id}" - - # --------------------------------------------------------------------------- -# Upload a single file (used by /api/upload for each item) +# Upload a single file # --------------------------------------------------------------------------- def handle_one_upload( api: HfApi, filename: str, content: bytes, - custom_id: Optional[str], - expires_in_minutes: Optional[int], + custom_id: Optional[str] ) -> dict: ext = os.path.splitext(filename)[1] item_id = sanitize_id(custom_id, ext) if custom_id else random_id(ext) - - expires_at = None - if expires_in_minutes and expires_in_minutes > 0: - expires_at = (datetime.now(timezone.utc) + timedelta(minutes=expires_in_minutes)).isoformat() - + upload_bytes(api, content, item_id) - write_meta(api, item_id, expires_at) - - link_path = f"/api/file/{item_id}" if expires_at else f"/{item_id}" + + # Since expiry is removed, all links just use the root static redirect + link_path = f"/{item_id}" + return { "item_id": item_id, "link_path": link_path, - "expires_at": expires_at, "size": len(content), } - # --------------------------------------------------------------------------- # Routes # --------------------------------------------------------------------------- @@ -135,13 +105,11 @@ def handle_one_upload( @app.post("/api/upload") async def upload_files( files: List[UploadFile] = File(...), - custom_id: Optional[str] = Form(None), - expires_in_minutes: Optional[int] = Form(None), + custom_id: Optional[str] = Form(None) ): api = get_api() ensure_repo(api) - # custom_id only makes sense for a single file if custom_id and len(files) > 1: raise HTTPException(status_code=400, detail="custom_id can only be used when uploading a single file") @@ -154,7 +122,7 @@ async def upload_files( detail=f"'{f.filename}' exceeds the {MAX_UPLOAD_BYTES // (1024 * 1024)}MB request limit", ) try: - result = handle_one_upload(api, f.filename or "file", content, custom_id, expires_in_minutes) + result = handle_one_upload(api, f.filename or "file", content, custom_id) results.append(result) except HTTPException: raise @@ -162,30 +130,25 @@ async def upload_files( raise HTTPException(status_code=500, detail=f"Failed to upload '{f.filename}': {e}") batch_path = None - batch_expires_at = None if len(results) > 1: batch_id = random_batch_id() - batch_expires_at = results[0].get("expires_at") # all files in a batch share the same expiry manifest = { "item_ids": [r["item_id"] for r in results], - "created_at": datetime.now(timezone.utc).isoformat(), - "expires_at": batch_expires_at, + "created_at": datetime.now(timezone.utc).isoformat() } upload_bytes(api, json.dumps(manifest).encode(), f"_batches/{batch_id}.json") batch_path = f"/batch/{batch_id}" - return {"files": results, "batch_path": batch_path, "batch_expires_at": batch_expires_at} - + return {"files": results, "batch_path": batch_path} @app.post("/api/upload-url") async def upload_from_url( url: str = Form(...), - custom_id: Optional[str] = Form(None), - expires_in_minutes: Optional[int] = Form(None), + custom_id: Optional[str] = Form(None) ): if not url.startswith(("http://", "https://")): raise HTTPException(status_code=400, detail="Only http/https URLs are supported") - + api = get_api() ensure_repo(api) @@ -204,9 +167,8 @@ async def upload_from_url( ) filename = url.split("/")[-1].split("?")[0] or "file" - try: - result = handle_one_upload(api, filename, content, custom_id, expires_in_minutes) + result = handle_one_upload(api, filename, content, custom_id) except HTTPException: raise except Exception as e: @@ -214,30 +176,6 @@ async def upload_from_url( return {"files": [result]} - -@app.get("/api/file/{item_id:path}") -async def serve_expiring_file(item_id: str): - """Dynamic redirect for links that were uploaded with an expiry.""" - try: - meta_path = hf_hub_download( - repo_id=DATASET_REPO, - repo_type="dataset", - filename=f"{item_id}.meta.json", - token=HF_TOKEN, - ) - with open(meta_path) as fh: - meta = json.load(fh) - expires_at = meta.get("expires_at") - if expires_at and datetime.fromisoformat(expires_at) < datetime.now(timezone.utc): - return JSONResponse(status_code=410, content={"detail": "This link has expired."}) - except EntryNotFoundError: - pass # no meta file -> treat as non-expiring - except Exception: - pass # if metadata can't be checked, fail open rather than block a valid file - - return RedirectResponse(url=raw_url(item_id), status_code=302) - - @app.get("/batch/{batch_id}", response_class=HTMLResponse) async def serve_batch(batch_id: str): """Renders a simple page listing every file uploaded together in one batch.""" @@ -247,6 +185,7 @@ async def serve_batch(batch_id: str): repo_type="dataset", filename=f"_batches/{batch_id}.json", token=HF_TOKEN, + cache_dir="/tmp" # Explicitly force /tmp to avoid Vercel Errno 30 ) with open(manifest_path) as fh: manifest = json.load(fh) @@ -255,25 +194,14 @@ async def serve_batch(batch_id: str): except Exception as e: raise HTTPException(status_code=500, detail=str(e)) - expires_at = manifest.get("expires_at") - if expires_at and datetime.fromisoformat(expires_at) < datetime.now(timezone.utc): - return HTMLResponse(status_code=410, content=_batch_html([], expired=True)) - item_ids = manifest.get("item_ids", []) - files = [] - for item_id in item_ids: - # if a file has an expiry meta, route through the checked path; otherwise link straight to HF - link_path = f"/api/file/{item_id}" - files.append({"item_id": item_id, "link_path": link_path}) + files = [{"item_id": i, "link_path": f"/{i}"} for i in item_ids] return HTMLResponse(content=_batch_html(files)) - -def _batch_html(files: list, not_found: bool = False, expired: bool = False) -> str: +def _batch_html(files: list, not_found: bool = False) -> str: if not_found: body = '<p class="empty">Batch not found.</p>' - elif expired: - body = '<p class="empty">This batch link has expired.</p>' elif not files: body = '<p class="empty">This batch is empty.</p>' else: @@ -299,11 +227,11 @@ def _batch_html(files: list, not_found: bool = False, expired: bool = False) -> .wrap {{ max-width: 520px; margin: 0 auto; }} h1 {{ font-size: 1.4rem; margin-bottom: 1.5rem; }} .list {{ display: flex; flex-direction: column; gap: 0.5rem; }} - .row {{ display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; + .row {{ display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; background: #F1F1EA; padding: 0.9rem 1rem; border-radius: 12px; }} @media (prefers-color-scheme: dark) {{ .row {{ background: #2D2F2B; }} }} .name {{ font-size: 0.85rem; word-break: break-all; }} - .open {{ flex-shrink: 0; background: #386A20; color: #fff; text-decoration: none; + .open {{ flex-shrink: 0; background: #386A20; color: #fff; text-decoration: none; font-size: 0.85rem; font-weight: 600; padding: 0.45rem 0.9rem; border-radius: 8px; }} @media (prefers-color-scheme: dark) {{ .open {{ background: #9CD67D; color: #0C3900; }} }} .empty {{ font-size: 0.9rem; opacity: 0.7; }}
© notamitgamer • Site Built: 2026-07-21 13:58:23 UTC • git-mirror commit: 1037f62 [view raw info]
Originally created with stagit • modified by notamitgamer
Forked from github.com/notamitgamer/git-mirror