mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-03 04:42:39 +02:00
feat: enable public access for podcasts in shared chats
This commit is contained in:
parent
7017a14107
commit
aeb0deb21e
3 changed files with 50 additions and 19 deletions
|
|
@ -25,7 +25,7 @@ from app.db import (
|
||||||
get_async_session,
|
get_async_session,
|
||||||
)
|
)
|
||||||
from app.schemas import PodcastRead
|
from app.schemas import PodcastRead
|
||||||
from app.users import current_active_user
|
from app.users import current_active_user, current_optional_user
|
||||||
from app.utils.rbac import check_permission
|
from app.utils.rbac import check_permission
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
@ -161,46 +161,49 @@ async def delete_podcast(
|
||||||
async def stream_podcast(
|
async def stream_podcast(
|
||||||
podcast_id: int,
|
podcast_id: int,
|
||||||
session: AsyncSession = Depends(get_async_session),
|
session: AsyncSession = Depends(get_async_session),
|
||||||
user: User = Depends(current_active_user),
|
user: User | None = Depends(current_optional_user),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Stream a podcast audio file.
|
Stream a podcast audio file.
|
||||||
Requires PODCASTS_READ permission for the search space.
|
|
||||||
|
Access is allowed if:
|
||||||
|
- User is authenticated with PODCASTS_READ permission, OR
|
||||||
|
- Podcast belongs to a publicly shared thread
|
||||||
|
|
||||||
Note: Both /stream and /audio endpoints are supported for compatibility.
|
Note: Both /stream and /audio endpoints are supported for compatibility.
|
||||||
"""
|
"""
|
||||||
|
from app.services.public_chat_service import is_podcast_publicly_accessible
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = await session.execute(select(Podcast).filter(Podcast.id == podcast_id))
|
result = await session.execute(select(Podcast).filter(Podcast.id == podcast_id))
|
||||||
podcast = result.scalars().first()
|
podcast = result.scalars().first()
|
||||||
|
|
||||||
if not podcast:
|
if not podcast:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=404, detail="Podcast not found")
|
||||||
status_code=404,
|
|
||||||
detail="Podcast not found",
|
is_public = await is_podcast_publicly_accessible(session, podcast_id)
|
||||||
|
|
||||||
|
if not is_public:
|
||||||
|
if not user:
|
||||||
|
raise HTTPException(status_code=401, detail="Authentication required")
|
||||||
|
|
||||||
|
await check_permission(
|
||||||
|
session,
|
||||||
|
user,
|
||||||
|
podcast.search_space_id,
|
||||||
|
Permission.PODCASTS_READ.value,
|
||||||
|
"You don't have permission to access podcasts in this search space",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check permission for the search space
|
|
||||||
await check_permission(
|
|
||||||
session,
|
|
||||||
user,
|
|
||||||
podcast.search_space_id,
|
|
||||||
Permission.PODCASTS_READ.value,
|
|
||||||
"You don't have permission to access podcasts in this search space",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Get the file path
|
|
||||||
file_path = podcast.file_location
|
file_path = podcast.file_location
|
||||||
|
|
||||||
# Check if the file exists
|
|
||||||
if not file_path or not os.path.isfile(file_path):
|
if not file_path or not os.path.isfile(file_path):
|
||||||
raise HTTPException(status_code=404, detail="Podcast audio file not found")
|
raise HTTPException(status_code=404, detail="Podcast audio file not found")
|
||||||
|
|
||||||
# Define a generator function to stream the file
|
|
||||||
def iterfile():
|
def iterfile():
|
||||||
with open(file_path, mode="rb") as file_like:
|
with open(file_path, mode="rb") as file_like:
|
||||||
yield from file_like
|
yield from file_like
|
||||||
|
|
||||||
# Return a streaming response with appropriate headers
|
|
||||||
return StreamingResponse(
|
return StreamingResponse(
|
||||||
iterfile(),
|
iterfile(),
|
||||||
media_type="audio/mpeg",
|
media_type="audio/mpeg",
|
||||||
|
|
|
||||||
|
|
@ -289,6 +289,7 @@ async def clone_public_chat(
|
||||||
session,
|
session,
|
||||||
old_podcast_id,
|
old_podcast_id,
|
||||||
target_search_space_id,
|
target_search_space_id,
|
||||||
|
new_thread.id,
|
||||||
)
|
)
|
||||||
if new_podcast_id:
|
if new_podcast_id:
|
||||||
podcast_id_map[old_podcast_id] = new_podcast_id
|
podcast_id_map[old_podcast_id] = new_podcast_id
|
||||||
|
|
@ -331,6 +332,7 @@ async def _clone_podcast(
|
||||||
session: AsyncSession,
|
session: AsyncSession,
|
||||||
podcast_id: int,
|
podcast_id: int,
|
||||||
target_search_space_id: int,
|
target_search_space_id: int,
|
||||||
|
target_thread_id: int,
|
||||||
) -> int | None:
|
) -> int | None:
|
||||||
"""Clone a podcast record and its audio file."""
|
"""Clone a podcast record and its audio file."""
|
||||||
import shutil
|
import shutil
|
||||||
|
|
@ -359,6 +361,7 @@ async def _clone_podcast(
|
||||||
podcast_transcript=original.podcast_transcript,
|
podcast_transcript=original.podcast_transcript,
|
||||||
file_location=new_file_path,
|
file_location=new_file_path,
|
||||||
search_space_id=target_search_space_id,
|
search_space_id=target_search_space_id,
|
||||||
|
thread_id=target_thread_id,
|
||||||
)
|
)
|
||||||
session.add(new_podcast)
|
session.add(new_podcast)
|
||||||
await session.flush()
|
await session.flush()
|
||||||
|
|
@ -412,3 +415,27 @@ async def _create_clone_failure_notification(
|
||||||
)
|
)
|
||||||
session.add(notification)
|
session.add(notification)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
async def is_podcast_publicly_accessible(
|
||||||
|
session: AsyncSession,
|
||||||
|
podcast_id: int,
|
||||||
|
) -> bool:
|
||||||
|
"""
|
||||||
|
Check if a podcast belongs to a publicly shared thread.
|
||||||
|
|
||||||
|
Uses the thread_id foreign key for efficient lookup.
|
||||||
|
"""
|
||||||
|
from app.db import Podcast
|
||||||
|
|
||||||
|
result = await session.execute(
|
||||||
|
select(Podcast)
|
||||||
|
.options(selectinload(Podcast.thread))
|
||||||
|
.filter(Podcast.id == podcast_id)
|
||||||
|
)
|
||||||
|
podcast = result.scalars().first()
|
||||||
|
|
||||||
|
if not podcast or not podcast.thread:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return podcast.thread.public_share_enabled
|
||||||
|
|
|
||||||
|
|
@ -229,3 +229,4 @@ auth_backend = AuthenticationBackend(
|
||||||
fastapi_users = FastAPIUsers[User, uuid.UUID](get_user_manager, [auth_backend])
|
fastapi_users = FastAPIUsers[User, uuid.UUID](get_user_manager, [auth_backend])
|
||||||
|
|
||||||
current_active_user = fastapi_users.current_user(active=True)
|
current_active_user = fastapi_users.current_user(active=True)
|
||||||
|
current_optional_user = fastapi_users.current_user(active=True, optional=True)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue