mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-03 12:52:39 +02:00
refactor: update public_chat_routes and schemas for snapshots
This commit is contained in:
parent
a45412abad
commit
e7242be763
2 changed files with 71 additions and 61 deletions
|
|
@ -1,21 +1,25 @@
|
|||
"""
|
||||
Routes for public chat access (unauthenticated and mixed-auth endpoints).
|
||||
Routes for public chat access via immutable snapshots.
|
||||
|
||||
All public endpoints use share_token for access - no authentication required
|
||||
for read operations. Clone requires authentication.
|
||||
"""
|
||||
|
||||
from datetime import UTC, datetime
|
||||
import os
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from fastapi.responses import StreamingResponse
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.db import ChatVisibility, NewChatThread, User, get_async_session
|
||||
from app.db import User, get_async_session
|
||||
from app.schemas.new_chat import (
|
||||
CloneInitResponse,
|
||||
CloneResponse,
|
||||
PublicChatResponse,
|
||||
)
|
||||
from app.services.public_chat_service import (
|
||||
clone_from_snapshot,
|
||||
get_public_chat,
|
||||
get_thread_by_share_token,
|
||||
get_user_default_search_space,
|
||||
get_snapshot_podcast,
|
||||
)
|
||||
from app.users import current_active_user
|
||||
|
||||
|
|
@ -28,57 +32,60 @@ async def read_public_chat(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
"""
|
||||
Get a public chat by share token.
|
||||
Get a public chat snapshot by share token.
|
||||
|
||||
No authentication required.
|
||||
Returns sanitized content (citations stripped).
|
||||
Returns immutable snapshot data (sanitized, citations stripped).
|
||||
"""
|
||||
return await get_public_chat(session, share_token)
|
||||
|
||||
|
||||
@router.post("/{share_token}/clone", response_model=CloneInitResponse)
|
||||
async def clone_public_chat_endpoint(
|
||||
@router.post("/{share_token}/clone", response_model=CloneResponse)
|
||||
async def clone_public_chat(
|
||||
share_token: str,
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
user: User = Depends(current_active_user),
|
||||
):
|
||||
"""
|
||||
Initialize cloning a public chat to the user's account.
|
||||
|
||||
Creates an empty thread with clone_pending=True.
|
||||
Frontend should redirect to the new thread and call /complete-clone.
|
||||
Clone a public chat snapshot to the user's account.
|
||||
|
||||
Single-phase clone: creates thread and copies messages in one request.
|
||||
Requires authentication.
|
||||
"""
|
||||
source_thread = await get_thread_by_share_token(session, share_token)
|
||||
return await clone_from_snapshot(session, share_token, user)
|
||||
|
||||
if not source_thread:
|
||||
raise HTTPException(
|
||||
status_code=404, detail="Chat not found or no longer public"
|
||||
)
|
||||
|
||||
target_search_space_id = await get_user_default_search_space(session, user.id)
|
||||
@router.get("/{share_token}/podcasts/{podcast_id}/stream")
|
||||
async def stream_public_podcast(
|
||||
share_token: str,
|
||||
podcast_id: int,
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
"""
|
||||
Stream a podcast from a public chat snapshot.
|
||||
|
||||
if target_search_space_id is None:
|
||||
raise HTTPException(status_code=400, detail="No search space found for user")
|
||||
No authentication required - the share_token provides access.
|
||||
Looks up podcast by original_id in the snapshot's podcasts array.
|
||||
"""
|
||||
podcast_info = await get_snapshot_podcast(session, share_token, podcast_id)
|
||||
|
||||
new_thread = NewChatThread(
|
||||
title=source_thread.title,
|
||||
archived=False,
|
||||
visibility=ChatVisibility.PRIVATE,
|
||||
search_space_id=target_search_space_id,
|
||||
created_by_id=user.id,
|
||||
public_share_enabled=False,
|
||||
cloned_from_thread_id=source_thread.id,
|
||||
cloned_at=datetime.now(UTC),
|
||||
clone_pending=True,
|
||||
)
|
||||
session.add(new_thread)
|
||||
await session.commit()
|
||||
await session.refresh(new_thread)
|
||||
|
||||
return CloneInitResponse(
|
||||
thread_id=new_thread.id,
|
||||
search_space_id=target_search_space_id,
|
||||
share_token=share_token,
|
||||
if not podcast_info:
|
||||
raise HTTPException(status_code=404, detail="Podcast not found")
|
||||
|
||||
file_path = podcast_info.get("file_path")
|
||||
|
||||
if not file_path or not os.path.isfile(file_path):
|
||||
raise HTTPException(status_code=404, detail="Podcast audio file not found")
|
||||
|
||||
def iterfile():
|
||||
with open(file_path, mode="rb") as file_like:
|
||||
yield from file_like
|
||||
|
||||
return StreamingResponse(
|
||||
iterfile(),
|
||||
media_type="audio/mpeg",
|
||||
headers={
|
||||
"Accept-Ranges": "bytes",
|
||||
"Content-Disposition": f"inline; filename={os.path.basename(file_path)}",
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -95,9 +95,6 @@ class NewChatThreadRead(NewChatThreadBase, IDModel):
|
|||
search_space_id: int
|
||||
visibility: ChatVisibility
|
||||
created_by_id: UUID | None = None
|
||||
public_share_enabled: bool = False
|
||||
public_share_token: str | None = None
|
||||
clone_pending: bool = False
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
|
|
@ -137,7 +134,6 @@ class ThreadListItem(BaseModel):
|
|||
visibility: ChatVisibility
|
||||
created_by_id: UUID | None = None
|
||||
is_own_thread: bool = False
|
||||
public_share_enabled: bool = False
|
||||
created_at: datetime = Field(alias="createdAt")
|
||||
updated_at: datetime = Field(alias="updatedAt")
|
||||
|
||||
|
|
@ -211,22 +207,33 @@ class RegenerateRequest(BaseModel):
|
|||
|
||||
|
||||
# =============================================================================
|
||||
# Public Sharing Schemas
|
||||
# Public Chat Snapshot Schemas
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class PublicShareToggleRequest(BaseModel):
|
||||
"""Request to enable/disable public sharing for a thread."""
|
||||
class SnapshotCreateResponse(BaseModel):
|
||||
"""Response after creating a public snapshot."""
|
||||
|
||||
enabled: bool
|
||||
snapshot_id: int
|
||||
share_token: str
|
||||
public_url: str
|
||||
is_new: bool # False if existing snapshot returned (same content)
|
||||
|
||||
|
||||
class PublicShareToggleResponse(BaseModel):
|
||||
"""Response after toggling public sharing."""
|
||||
class SnapshotInfo(BaseModel):
|
||||
"""Info about a single snapshot."""
|
||||
|
||||
enabled: bool
|
||||
public_url: str | None = None
|
||||
share_token: str | None = None
|
||||
id: int
|
||||
share_token: str
|
||||
public_url: str
|
||||
created_at: datetime
|
||||
message_count: int
|
||||
|
||||
|
||||
class SnapshotListResponse(BaseModel):
|
||||
"""List of snapshots for a thread."""
|
||||
|
||||
snapshots: list[SnapshotInfo]
|
||||
|
||||
|
||||
# =============================================================================
|
||||
|
|
@ -256,12 +263,8 @@ class PublicChatResponse(BaseModel):
|
|||
messages: list[PublicChatMessage]
|
||||
|
||||
|
||||
class CloneInitResponse(BaseModel):
|
||||
class CloneResponse(BaseModel):
|
||||
"""Response after cloning a public snapshot."""
|
||||
|
||||
thread_id: int
|
||||
search_space_id: int
|
||||
share_token: str
|
||||
|
||||
|
||||
class CompleteCloneResponse(BaseModel):
|
||||
status: str
|
||||
message_count: int
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue