feat: GET /transcripts/{filename} — serve transcript content
This commit is contained in:
+11
-1
@@ -6,7 +6,7 @@ from fastapi import APIRouter, WebSocket, WebSocketDisconnect, Depends, HTTPExce
|
|||||||
|
|
||||||
from api.state import state, Status
|
from api.state import state, Status
|
||||||
from config import load as load_config
|
from config import load as load_config
|
||||||
from output import list_transcripts
|
from output import list_transcripts, read_transcript
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
_ws_clients: list[WebSocket] = []
|
_ws_clients: list[WebSocket] = []
|
||||||
@@ -124,6 +124,16 @@ async def get_transcripts(user: dict = Depends(current_user)):
|
|||||||
return list_transcripts(user_dir)
|
return list_transcripts(user_dir)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/transcripts/{filename}")
|
||||||
|
async def get_transcript(filename: str, user: dict = Depends(current_user)):
|
||||||
|
from fastapi.responses import PlainTextResponse
|
||||||
|
user_dir = os.path.join(user["output_dir"], user["username"])
|
||||||
|
content = read_transcript(user_dir, filename)
|
||||||
|
if content is None:
|
||||||
|
raise HTTPException(status_code=404, detail="Nicht gefunden")
|
||||||
|
return PlainTextResponse(content)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/config")
|
@router.get("/config")
|
||||||
async def get_config(user: dict = Depends(current_user)):
|
async def get_config(user: dict = Depends(current_user)):
|
||||||
return load_config()
|
return load_config()
|
||||||
|
|||||||
@@ -35,6 +35,17 @@ def save_transcript(
|
|||||||
return path
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def read_transcript(output_dir: str, filename: str) -> str | None:
|
||||||
|
"""Return file content if filename is a plain .md file inside output_dir."""
|
||||||
|
if os.path.basename(filename) != filename or not filename.endswith(".md"):
|
||||||
|
return None
|
||||||
|
path = os.path.join(output_dir, filename)
|
||||||
|
if not os.path.exists(path):
|
||||||
|
return None
|
||||||
|
with open(path, encoding="utf-8") as f:
|
||||||
|
return f.read()
|
||||||
|
|
||||||
|
|
||||||
def list_transcripts(output_dir: str, limit: int = 20) -> list[dict]:
|
def list_transcripts(output_dir: str, limit: int = 20) -> list[dict]:
|
||||||
if not os.path.exists(output_dir):
|
if not os.path.exists(output_dir):
|
||||||
return []
|
return []
|
||||||
|
|||||||
@@ -45,6 +45,30 @@ def test_status_requires_auth():
|
|||||||
assert r.status_code == 401
|
assert r.status_code == 401
|
||||||
|
|
||||||
|
|
||||||
|
def make_app_for_dir(output_dir: str):
|
||||||
|
from fastapi import FastAPI
|
||||||
|
from api.router import router, current_user
|
||||||
|
app = FastAPI()
|
||||||
|
app.dependency_overrides[current_user] = lambda: {"username": "", "output_dir": output_dir, "is_admin": False}
|
||||||
|
app.include_router(router)
|
||||||
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_transcript_returns_content(tmp_path):
|
||||||
|
f = tmp_path / "2026-01-01-0900-test.md"
|
||||||
|
f.write_text("# Hello\n\ncontent here\n")
|
||||||
|
client = TestClient(make_app_for_dir(str(tmp_path)))
|
||||||
|
r = client.get("/transcripts/2026-01-01-0900-test.md")
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert "Hello" in r.text
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_transcript_rejects_path_traversal(tmp_path):
|
||||||
|
client = TestClient(make_app_for_dir(str(tmp_path)))
|
||||||
|
r = client.get("/transcripts/..%2Fsecret.md")
|
||||||
|
assert r.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
def test_login_rejects_wrong_credentials():
|
def test_login_rejects_wrong_credentials():
|
||||||
import tempfile, os
|
import tempfile, os
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|||||||
Reference in New Issue
Block a user