summaryrefslogtreecommitdiff
path: root/backend/app
diff options
context:
space:
mode:
Diffstat (limited to 'backend/app')
-rw-r--r--backend/app/main.py100
1 files changed, 100 insertions, 0 deletions
diff --git a/backend/app/main.py b/backend/app/main.py
index 261b45a..886bd9e 100644
--- a/backend/app/main.py
+++ b/backend/app/main.py
@@ -1,6 +1,7 @@
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse, FileResponse
+from fastapi import UploadFile, File
from pydantic import BaseModel
from app.schemas import NodeRunRequest, NodeRunResponse, MergeStrategy, Role, Message, Context, LLMConfig, ModelProvider, ReasoningEffort
from app.services.llm import llm_streamer, generate_title
@@ -9,6 +10,7 @@ import os
import json
import shutil
from typing import List, Literal, Optional
+from uuid import uuid4
load_dotenv()
@@ -49,6 +51,12 @@ def archive_root(user: str) -> str:
return os.path.join(ensure_user_root(user), "archive")
+def files_root(user: str) -> str:
+ root = os.path.join(ensure_user_root(user), "files")
+ os.makedirs(root, exist_ok=True)
+ return root
+
+
def migrate_legacy_layout(user: str):
"""
Migrate from legacy ./projects/<user> and legacy archive folders to the new data/<user>/ structure.
@@ -136,6 +144,15 @@ class RenameRequest(BaseModel):
new_name: Optional[str] = None
new_path: Optional[str] = None
+class FileMeta(BaseModel):
+ id: str
+ name: str
+ size: int
+ mime: str
+ created_at: float
+ provider: Optional[str] = None
+ provider_file_id: Optional[str] = None
+
class FolderRequest(BaseModel):
user: str = DEFAULT_USER
path: str # relative folder path
@@ -375,6 +392,27 @@ def archived_path(user: str) -> str:
root = archive_root(user)
return os.path.join(root, ARCHIVE_FILENAME)
+# ---------------- Files (uploads) ----------------
+def files_index_path(user: str) -> str:
+ return os.path.join(files_root(user), "index.json")
+
+
+def load_files_index(user: str) -> List[FileMeta]:
+ path = files_index_path(user)
+ if not os.path.exists(path):
+ return []
+ with open(path, "r", encoding="utf-8") as f:
+ data = json.load(f)
+ return [FileMeta(**item) for item in data]
+
+
+def save_files_index(user: str, items: List[FileMeta]):
+ path = files_index_path(user)
+ os.makedirs(os.path.dirname(path), exist_ok=True)
+ with open(path, "w", encoding="utf-8") as f:
+ json.dump([item.model_dump() for item in items], f, ensure_ascii=False, indent=2)
+
+# -------------------------------------------------
@app.get("/api/projects/archived")
def get_archived_nodes(user: str = DEFAULT_USER):
@@ -401,3 +439,65 @@ def save_archived_nodes(payload: dict):
return {"ok": True}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
+
+
+@app.get("/api/files")
+def list_files(user: str = DEFAULT_USER):
+ migrate_legacy_layout(user)
+ items = load_files_index(user)
+ return {"files": [item.model_dump() for item in items]}
+
+
+@app.post("/api/files/upload")
+async def upload_file(user: str = DEFAULT_USER, file: UploadFile = File(...)):
+ migrate_legacy_layout(user)
+ items = load_files_index(user)
+ file_id = str(uuid4())
+ dest_root = files_root(user)
+ dest_path = os.path.join(dest_root, file_id)
+ try:
+ content = await file.read()
+ size = len(content)
+ with open(dest_path, "wb") as f:
+ f.write(content)
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=str(e))
+
+ meta = FileMeta(
+ id=file_id,
+ name=file.filename,
+ size=size,
+ mime=file.content_type or "application/octet-stream",
+ created_at=os.path.getmtime(dest_path),
+ )
+ items.append(meta)
+ save_files_index(user, items)
+ return {"file": meta}
+
+
+@app.get("/api/files/download")
+def download_file(user: str = DEFAULT_USER, file_id: str = ""):
+ migrate_legacy_layout(user)
+ items = load_files_index(user)
+ meta = next((i for i in items if i.id == file_id), None)
+ if not meta:
+ raise HTTPException(status_code=404, detail="file not found")
+ path = os.path.join(files_root(user), file_id)
+ if not os.path.exists(path):
+ raise HTTPException(status_code=404, detail="file missing on disk")
+ return FileResponse(path, filename=meta.name, media_type=meta.mime)
+
+
+@app.post("/api/files/delete")
+def delete_file(user: str = DEFAULT_USER, file_id: str = ""):
+ migrate_legacy_layout(user)
+ items = load_files_index(user)
+ meta = next((i for i in items if i.id == file_id), None)
+ if not meta:
+ raise HTTPException(status_code=404, detail="file not found")
+ path = os.path.join(files_root(user), file_id)
+ if os.path.exists(path):
+ os.remove(path)
+ items = [i for i in items if i.id != file_id]
+ save_files_index(user, items)
+ return {"ok": True}