mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-03 04:42: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 import APIRouter, Depends, HTTPException
|
||||||
|
from fastapi.responses import StreamingResponse
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
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 (
|
from app.schemas.new_chat import (
|
||||||
CloneInitResponse,
|
CloneResponse,
|
||||||
PublicChatResponse,
|
PublicChatResponse,
|
||||||
)
|
)
|
||||||
from app.services.public_chat_service import (
|
from app.services.public_chat_service import (
|
||||||
|
clone_from_snapshot,
|
||||||
get_public_chat,
|
get_public_chat,
|
||||||
get_thread_by_share_token,
|
get_snapshot_podcast,
|
||||||
get_user_default_search_space,
|
|
||||||
)
|
)
|
||||||
from app.users import current_active_user
|
from app.users import current_active_user
|
||||||
|
|
||||||
|
|
@ -28,57 +32,60 @@ async def read_public_chat(
|
||||||
session: AsyncSession = Depends(get_async_session),
|
session: AsyncSession = Depends(get_async_session),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Get a public chat by share token.
|
Get a public chat snapshot by share token.
|
||||||
|
|
||||||
No authentication required.
|
No authentication required.
|
||||||
Returns sanitized content (citations stripped).
|
Returns immutable snapshot data (sanitized, citations stripped).
|
||||||
"""
|
"""
|
||||||
return await get_public_chat(session, share_token)
|
return await get_public_chat(session, share_token)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{share_token}/clone", response_model=CloneInitResponse)
|
@router.post("/{share_token}/clone", response_model=CloneResponse)
|
||||||
async def clone_public_chat_endpoint(
|
async def clone_public_chat(
|
||||||
share_token: str,
|
share_token: str,
|
||||||
session: AsyncSession = Depends(get_async_session),
|
session: AsyncSession = Depends(get_async_session),
|
||||||
user: User = Depends(current_active_user),
|
user: User = Depends(current_active_user),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Initialize cloning a public chat to the user's account.
|
Clone a public chat snapshot to the user's account.
|
||||||
|
|
||||||
Creates an empty thread with clone_pending=True.
|
|
||||||
Frontend should redirect to the new thread and call /complete-clone.
|
|
||||||
|
|
||||||
|
Single-phase clone: creates thread and copies messages in one request.
|
||||||
Requires authentication.
|
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:
|
No authentication required - the share_token provides access.
|
||||||
raise HTTPException(status_code=400, detail="No search space found for user")
|
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(
|
if not podcast_info:
|
||||||
title=source_thread.title,
|
raise HTTPException(status_code=404, detail="Podcast not found")
|
||||||
archived=False,
|
|
||||||
visibility=ChatVisibility.PRIVATE,
|
file_path = podcast_info.get("file_path")
|
||||||
search_space_id=target_search_space_id,
|
|
||||||
created_by_id=user.id,
|
if not file_path or not os.path.isfile(file_path):
|
||||||
public_share_enabled=False,
|
raise HTTPException(status_code=404, detail="Podcast audio file not found")
|
||||||
cloned_from_thread_id=source_thread.id,
|
|
||||||
cloned_at=datetime.now(UTC),
|
def iterfile():
|
||||||
clone_pending=True,
|
with open(file_path, mode="rb") as file_like:
|
||||||
)
|
yield from file_like
|
||||||
session.add(new_thread)
|
|
||||||
await session.commit()
|
return StreamingResponse(
|
||||||
await session.refresh(new_thread)
|
iterfile(),
|
||||||
|
media_type="audio/mpeg",
|
||||||
return CloneInitResponse(
|
headers={
|
||||||
thread_id=new_thread.id,
|
"Accept-Ranges": "bytes",
|
||||||
search_space_id=target_search_space_id,
|
"Content-Disposition": f"inline; filename={os.path.basename(file_path)}",
|
||||||
share_token=share_token,
|
},
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -95,9 +95,6 @@ class NewChatThreadRead(NewChatThreadBase, IDModel):
|
||||||
search_space_id: int
|
search_space_id: int
|
||||||
visibility: ChatVisibility
|
visibility: ChatVisibility
|
||||||
created_by_id: UUID | None = None
|
created_by_id: UUID | None = None
|
||||||
public_share_enabled: bool = False
|
|
||||||
public_share_token: str | None = None
|
|
||||||
clone_pending: bool = False
|
|
||||||
created_at: datetime
|
created_at: datetime
|
||||||
updated_at: datetime
|
updated_at: datetime
|
||||||
|
|
||||||
|
|
@ -137,7 +134,6 @@ class ThreadListItem(BaseModel):
|
||||||
visibility: ChatVisibility
|
visibility: ChatVisibility
|
||||||
created_by_id: UUID | None = None
|
created_by_id: UUID | None = None
|
||||||
is_own_thread: bool = False
|
is_own_thread: bool = False
|
||||||
public_share_enabled: bool = False
|
|
||||||
created_at: datetime = Field(alias="createdAt")
|
created_at: datetime = Field(alias="createdAt")
|
||||||
updated_at: datetime = Field(alias="updatedAt")
|
updated_at: datetime = Field(alias="updatedAt")
|
||||||
|
|
||||||
|
|
@ -211,22 +207,33 @@ class RegenerateRequest(BaseModel):
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Public Sharing Schemas
|
# Public Chat Snapshot Schemas
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
|
|
||||||
class PublicShareToggleRequest(BaseModel):
|
class SnapshotCreateResponse(BaseModel):
|
||||||
"""Request to enable/disable public sharing for a thread."""
|
"""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):
|
class SnapshotInfo(BaseModel):
|
||||||
"""Response after toggling public sharing."""
|
"""Info about a single snapshot."""
|
||||||
|
|
||||||
enabled: bool
|
id: int
|
||||||
public_url: str | None = None
|
share_token: str
|
||||||
share_token: str | None = None
|
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]
|
messages: list[PublicChatMessage]
|
||||||
|
|
||||||
|
|
||||||
class CloneInitResponse(BaseModel):
|
class CloneResponse(BaseModel):
|
||||||
|
"""Response after cloning a public snapshot."""
|
||||||
|
|
||||||
thread_id: int
|
thread_id: int
|
||||||
search_space_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