Skip to content

Commit bf00a38

Browse files
committed
refac
1 parent 4a98745 commit bf00a38

3 files changed

Lines changed: 88 additions & 11 deletions

File tree

README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,22 @@ curl -X POST "http://localhost:8000/files/upload?path=/tmp/data.csv" \
7676
-F "file=@local_file.csv"
7777
```
7878

79+
**Via temporary link (no auth needed to upload):**
80+
```bash
81+
# 1. Generate an upload link
82+
curl -X POST "http://localhost:8000/files/upload/link?path=/tmp/data.csv" \
83+
-H "Authorization: Bearer <api-key>"
84+
# → {"url": "http://localhost:8000/files/upload/a1b2c3d4..."}
85+
86+
# 2. Upload to the link (no auth required)
87+
curl -X POST "http://localhost:8000/files/upload/a1b2c3d4..." \
88+
-F "file=@local_file.csv"
89+
```
90+
7991
### Download a File
8092

8193
```bash
82-
curl "http://localhost:8000/files/download?path=/tmp/output.csv" \
94+
curl "http://localhost:8000/files/download/link?path=/tmp/output.csv" \
8395
-H "Authorization: Bearer <api-key>"
8496
```
8597

open_terminal/main.py

Lines changed: 73 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from fastapi import Depends, FastAPI, File, HTTPException, Query, Request, UploadFile
77
from fastapi.middleware.cors import CORSMiddleware
8-
from fastapi.responses import FileResponse, StreamingResponse
8+
from fastapi.responses import FileResponse, HTMLResponse, StreamingResponse
99
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
1010
from pydantic import BaseModel, Field
1111

@@ -69,12 +69,13 @@ async def health():
6969

7070

7171

72-
# Temporary download links: {token: (path, expiry_timestamp)}
72+
# Temporary links: {token: (path, expiry_timestamp)}
7373
_download_links: dict[str, tuple[str, float]] = {}
74+
_upload_links: dict[str, tuple[str, float]] = {}
7475

7576

7677
@app.get(
77-
"/files/download",
78+
"/files/download/link",
7879
summary="Get a file download link",
7980
description="Returns a temporary download URL for a file. Link expires after 5 minutes and requires no authentication to use.",
8081
dependencies=[Depends(verify_api_key)],
@@ -102,11 +103,7 @@ async def get_file_link(
102103

103104
@app.get(
104105
"/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-
},
106+
include_in_schema=False,
110107
)
111108
async def download_file(token: str):
112109
import time
@@ -157,6 +154,74 @@ async def upload_file(
157154
return {"path": path, "size": len(content)}
158155

159156

157+
@app.post(
158+
"/files/upload/link",
159+
summary="Create an upload link",
160+
description="Generate a temporary, unauthenticated upload URL. Link expires after 5 minutes.",
161+
dependencies=[Depends(verify_api_key)],
162+
responses={
163+
401: {"description": "Invalid or missing API key."},
164+
},
165+
)
166+
async def create_upload_link(
167+
path: str = Query(..., description="Absolute destination path for the uploaded file."),
168+
request: Request = None,
169+
):
170+
import time
171+
import uuid
172+
173+
token = uuid.uuid4().hex
174+
_upload_links[token] = (path, time.time() + 300)
175+
176+
base_url = str(request.base_url).rstrip("/")
177+
return {"url": f"{base_url}/files/upload/{token}"}
178+
179+
180+
@app.get(
181+
"/files/upload/{token}",
182+
response_class=HTMLResponse,
183+
include_in_schema=False,
184+
)
185+
async def upload_page(token: str):
186+
import time
187+
188+
entry = _upload_links.get(token)
189+
if not entry or time.time() > entry[1]:
190+
return HTMLResponse("Link expired.", status_code=404)
191+
192+
return HTMLResponse(
193+
'<form method="post" enctype="multipart/form-data">'
194+
'<input type="file" name="file" required> '
195+
'<button type="submit">Upload</button>'
196+
'</form>'
197+
)
198+
199+
200+
@app.post(
201+
"/files/upload/{token}",
202+
include_in_schema=False,
203+
)
204+
async def upload_file_via_link(
205+
token: str,
206+
file: UploadFile = File(..., description="The file to upload."),
207+
):
208+
import time
209+
210+
entry = _upload_links.pop(token, None)
211+
if not entry:
212+
raise HTTPException(status_code=404, detail="Invalid or expired upload link")
213+
214+
path, expiry = entry
215+
if time.time() > expiry:
216+
raise HTTPException(status_code=404, detail="Upload link expired")
217+
218+
os.makedirs(os.path.dirname(path), exist_ok=True)
219+
content = await file.read()
220+
with open(path, "wb") as f:
221+
f.write(content)
222+
return {"path": path, "size": len(content)}
223+
224+
160225
@app.post(
161226
"/execute",
162227
summary="Execute a command",

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[project]
22
name = "open-terminal"
3-
version = "0.1.5"
4-
description = "A barebones terminal interaction API"
3+
version = "0.1.7"
4+
description = "A minimal terminal interaction API"
55
readme = "README.md"
66
authors = [
77
{ name = "Timothy Jaeryang Baek", email = "tim@openwebui.com" }

0 commit comments

Comments
 (0)