mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-06-07 07:55:16 +02:00
* feat: add stt evals * add smart turn as provider * chore: remove deprecations * chore: format files * fix: remove deprecated UserIdleProcessor * fix: remove deprecated TranscriptProcessor * chore: update pipecat submodule * feat: add evals visualisation * fix: trigger llm generation on client connected and pipeline started * chore: update pipecat * chore: update pipecat submodule * Add tests * fix: slow loading of workflow page * chore: update pipecat submodule * Show version after release * Fixes #99 * fix: provider check for websocket connection * Fixes #107 * Fix #96 * chore: fix documentation * fix: cloudonix campaign call error --------- Co-authored-by: Sabiha Khan <sabihak89@gmail.com>
95 lines
3.3 KiB
Python
95 lines
3.3 KiB
Python
"""Public download endpoints for workflow recordings and transcripts.
|
|
|
|
These endpoints provide secure, token-based public access to workflow artifacts
|
|
without requiring authentication. Tokens are generated on-demand when webhooks
|
|
are executed and included in the webhook payload.
|
|
"""
|
|
|
|
from typing import Literal
|
|
|
|
from fastapi import APIRouter, HTTPException, Query
|
|
from fastapi.responses import RedirectResponse
|
|
from loguru import logger
|
|
|
|
from api.db import db_client
|
|
from api.services.storage import get_storage_for_backend
|
|
|
|
router = APIRouter(prefix="/public/download")
|
|
|
|
|
|
@router.get("/workflow/{token}/{artifact_type}")
|
|
async def download_workflow_artifact(
|
|
token: str,
|
|
artifact_type: Literal["recording", "transcript"],
|
|
inline: bool = Query(
|
|
default=False, description="Display inline in browser instead of download"
|
|
),
|
|
):
|
|
"""Download a workflow recording or transcript via public access token.
|
|
|
|
This endpoint:
|
|
1. Validates the public access token
|
|
2. Looks up the corresponding workflow run
|
|
3. Generates a signed URL for the requested artifact
|
|
4. Redirects to the signed URL
|
|
|
|
Args:
|
|
token: The public access token (UUID format)
|
|
artifact_type: Type of artifact - "recording" or "transcript"
|
|
inline: If true, sets Content-Disposition to inline for browser preview
|
|
|
|
Returns:
|
|
RedirectResponse to the signed URL (302 redirect)
|
|
|
|
Raises:
|
|
HTTPException 404: If token is invalid or artifact not found
|
|
"""
|
|
# 1. Lookup workflow run by token
|
|
workflow_run = await db_client.get_workflow_run_by_public_token(token)
|
|
if not workflow_run:
|
|
logger.warning(f"Invalid public access token: {token[:8]}...")
|
|
raise HTTPException(status_code=404, detail="Invalid or expired token")
|
|
|
|
# 2. Get file path based on artifact type
|
|
if artifact_type == "recording":
|
|
file_path = workflow_run.recording_url
|
|
else: # transcript
|
|
file_path = workflow_run.transcript_url
|
|
|
|
if not file_path:
|
|
logger.warning(
|
|
f"Artifact not found: type={artifact_type}, workflow_run_id={workflow_run.id}"
|
|
)
|
|
raise HTTPException(
|
|
status_code=404,
|
|
detail=f"No {artifact_type} available for this workflow run",
|
|
)
|
|
|
|
# 3. Get storage backend for this workflow run
|
|
try:
|
|
storage = get_storage_for_backend(workflow_run.storage_backend)
|
|
except ValueError as e:
|
|
logger.error(f"Invalid storage backend: {workflow_run.storage_backend}")
|
|
raise HTTPException(status_code=500, detail="Storage configuration error")
|
|
|
|
# 4. Generate signed URL (1 hour expiration)
|
|
try:
|
|
signed_url = await storage.aget_signed_url(
|
|
file_path=file_path,
|
|
expiration=3600, # 1 hour
|
|
force_inline=inline,
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"Failed to generate signed URL: {e}")
|
|
raise HTTPException(status_code=500, detail="Failed to generate download URL")
|
|
|
|
if not signed_url:
|
|
logger.error(f"Storage returned None for signed URL: {file_path}")
|
|
raise HTTPException(status_code=500, detail="Failed to generate download URL")
|
|
|
|
logger.info(
|
|
f"Generated signed URL for {artifact_type}: workflow_run_id={workflow_run.id}, token={token[:8]}..."
|
|
)
|
|
|
|
# 5. Redirect to signed URL
|
|
return RedirectResponse(url=signed_url, status_code=302)
|