|
1 | 1 | import asyncio |
2 | 2 | import json |
| 3 | +import os |
3 | 4 | from typing import Optional |
4 | 5 |
|
5 | | -from fastapi import Depends, FastAPI, HTTPException, Query |
| 6 | +from fastapi import Depends, FastAPI, HTTPException, Query, Request |
6 | 7 | from fastapi.middleware.cors import CORSMiddleware |
7 | | -from fastapi.responses import StreamingResponse |
| 8 | +from fastapi.responses import FileResponse, StreamingResponse |
8 | 9 | from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer |
9 | 10 | from pydantic import BaseModel, Field |
10 | 11 |
|
@@ -68,6 +69,62 @@ async def health(): |
68 | 69 | return {"status": "ok"} |
69 | 70 |
|
70 | 71 |
|
| 72 | + |
| 73 | +# Temporary download links: {token: (path, expiry_timestamp)} |
| 74 | +_download_links: dict[str, tuple[str, float]] = {} |
| 75 | + |
| 76 | + |
| 77 | +@app.get( |
| 78 | + "/files", |
| 79 | + summary="Get a file download link", |
| 80 | + description="Returns a temporary download URL for a file. Link expires after 5 minutes and requires no authentication to use.", |
| 81 | + responses={ |
| 82 | + 404: {"description": "File not found."}, |
| 83 | + 401: {"description": "Invalid or missing API key."}, |
| 84 | + }, |
| 85 | +) |
| 86 | +async def get_file_link( |
| 87 | + path: str = Query(..., description="Absolute path to the file."), |
| 88 | + request: Request = None, |
| 89 | +): |
| 90 | + import time |
| 91 | + import uuid |
| 92 | + |
| 93 | + if not os.path.isfile(path): |
| 94 | + raise HTTPException(status_code=404, detail="File not found") |
| 95 | + |
| 96 | + token = uuid.uuid4().hex |
| 97 | + _download_links[token] = (path, time.time() + 300) |
| 98 | + |
| 99 | + base_url = str(request.base_url).rstrip("/") |
| 100 | + return {"url": f"{base_url}/files/download/{token}"} |
| 101 | + |
| 102 | + |
| 103 | +@app.get( |
| 104 | + "/files/download/{token}", |
| 105 | + summary="Download via link", |
| 106 | + description="Download a file using a temporary token. No authentication required.", |
| 107 | + responses={ |
| 108 | + 404: {"description": "Invalid or expired download link."}, |
| 109 | + }, |
| 110 | +) |
| 111 | +async def download_file(token: str): |
| 112 | + import time |
| 113 | + |
| 114 | + entry = _download_links.pop(token, None) |
| 115 | + if not entry: |
| 116 | + raise HTTPException(status_code=404, detail="Invalid or expired download link") |
| 117 | + |
| 118 | + path, expiry = entry |
| 119 | + if time.time() > expiry: |
| 120 | + raise HTTPException(status_code=404, detail="Download link expired") |
| 121 | + |
| 122 | + if not os.path.isfile(path): |
| 123 | + raise HTTPException(status_code=404, detail="File not found") |
| 124 | + |
| 125 | + return FileResponse(path) |
| 126 | + |
| 127 | + |
71 | 128 | @app.post( |
72 | 129 | "/execute", |
73 | 130 | summary="Execute a command", |
|
0 commit comments