kin: KIN-089 При попытке добавить креды прод сервера для проекта corelock вылетает 500 Internal Server Error
This commit is contained in:
parent
e80e50ba0c
commit
4a65d90218
13 changed files with 1215 additions and 4 deletions
109
web/api.py
109
web/api.py
|
|
@ -4,6 +4,7 @@ Run: uvicorn web.api:app --reload --port 8420
|
|||
"""
|
||||
|
||||
import logging
|
||||
import mimetypes
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
|
@ -12,7 +13,7 @@ from pathlib import Path
|
|||
# Ensure project root on sys.path
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
|
||||
from fastapi import FastAPI, HTTPException, Query
|
||||
from fastapi import FastAPI, File, HTTPException, Query, UploadFile
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import JSONResponse, FileResponse, Response
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
|
@ -1321,6 +1322,112 @@ def get_notifications(project_id: str | None = None):
|
|||
return notifications
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Attachments (KIN-090)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_MAX_ATTACHMENT_SIZE = 10 * 1024 * 1024 # 10 MB
|
||||
|
||||
|
||||
def _attachment_dir(project_path: Path, task_id: str) -> Path:
|
||||
"""Return (and create) {project_path}/.kin/attachments/{task_id}/."""
|
||||
d = project_path / ".kin" / "attachments" / task_id
|
||||
d.mkdir(parents=True, exist_ok=True)
|
||||
return d
|
||||
|
||||
|
||||
@app.post("/api/tasks/{task_id}/attachments", status_code=201)
|
||||
async def upload_attachment(task_id: str, file: UploadFile = File(...)):
|
||||
conn = get_conn()
|
||||
t = models.get_task(conn, task_id)
|
||||
if not t:
|
||||
conn.close()
|
||||
raise HTTPException(404, f"Task '{task_id}' not found")
|
||||
p = models.get_project(conn, t["project_id"])
|
||||
if not p or not p.get("path"):
|
||||
conn.close()
|
||||
raise HTTPException(400, "Attachments not supported for operations projects")
|
||||
|
||||
# Sanitize filename: strip directory components
|
||||
safe_name = Path(file.filename or "upload").name
|
||||
if not safe_name:
|
||||
conn.close()
|
||||
raise HTTPException(400, "Invalid filename")
|
||||
|
||||
att_dir = _attachment_dir(Path(p["path"]), task_id)
|
||||
dest = att_dir / safe_name
|
||||
|
||||
# Path traversal guard
|
||||
if not dest.is_relative_to(att_dir):
|
||||
conn.close()
|
||||
raise HTTPException(400, "Invalid filename")
|
||||
|
||||
# Read with size limit
|
||||
content = await file.read(_MAX_ATTACHMENT_SIZE + 1)
|
||||
if len(content) > _MAX_ATTACHMENT_SIZE:
|
||||
conn.close()
|
||||
raise HTTPException(413, f"File too large. Maximum size is {_MAX_ATTACHMENT_SIZE // (1024*1024)} MB")
|
||||
|
||||
dest.write_bytes(content)
|
||||
|
||||
mime_type = mimetypes.guess_type(safe_name)[0] or "application/octet-stream"
|
||||
attachment = models.create_attachment(
|
||||
conn, task_id,
|
||||
filename=safe_name,
|
||||
path=str(dest),
|
||||
mime_type=mime_type,
|
||||
size=len(content),
|
||||
)
|
||||
conn.close()
|
||||
return JSONResponse(attachment, status_code=201)
|
||||
|
||||
|
||||
@app.get("/api/tasks/{task_id}/attachments")
|
||||
def list_task_attachments(task_id: str):
|
||||
conn = get_conn()
|
||||
t = models.get_task(conn, task_id)
|
||||
if not t:
|
||||
conn.close()
|
||||
raise HTTPException(404, f"Task '{task_id}' not found")
|
||||
attachments = models.list_attachments(conn, task_id)
|
||||
conn.close()
|
||||
return attachments
|
||||
|
||||
|
||||
@app.delete("/api/tasks/{task_id}/attachments/{attachment_id}", status_code=204)
|
||||
def delete_task_attachment(task_id: str, attachment_id: int):
|
||||
conn = get_conn()
|
||||
att = models.get_attachment(conn, attachment_id)
|
||||
if not att or att["task_id"] != task_id:
|
||||
conn.close()
|
||||
raise HTTPException(404, f"Attachment #{attachment_id} not found")
|
||||
# Delete file from disk
|
||||
try:
|
||||
Path(att["path"]).unlink(missing_ok=True)
|
||||
except Exception:
|
||||
pass
|
||||
models.delete_attachment(conn, attachment_id)
|
||||
conn.close()
|
||||
return Response(status_code=204)
|
||||
|
||||
|
||||
@app.get("/api/attachments/{attachment_id}/file")
|
||||
def get_attachment_file(attachment_id: int):
|
||||
conn = get_conn()
|
||||
att = models.get_attachment(conn, attachment_id)
|
||||
conn.close()
|
||||
if not att:
|
||||
raise HTTPException(404, f"Attachment #{attachment_id} not found")
|
||||
file_path = Path(att["path"])
|
||||
if not file_path.exists():
|
||||
raise HTTPException(404, "Attachment file not found on disk")
|
||||
return FileResponse(
|
||||
str(file_path),
|
||||
media_type=att["mime_type"],
|
||||
filename=att["filename"],
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Chat (KIN-OBS-012)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue