feat(search): add unified global search for users/chats/messages
Some checks failed
CI / test (push) Failing after 24s
Some checks failed
CI / test (push) Failing after 24s
This commit is contained in:
2
app/search/__init__.py
Normal file
2
app/search/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
__all__ = []
|
||||
|
||||
30
app/search/router.py
Normal file
30
app/search/router.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.auth.service import get_current_user
|
||||
from app.database.session import get_db
|
||||
from app.search.schemas import GlobalSearchRead
|
||||
from app.search.service import global_search
|
||||
from app.users.models import User
|
||||
|
||||
router = APIRouter(prefix="/search", tags=["search"])
|
||||
|
||||
|
||||
@router.get("", response_model=GlobalSearchRead)
|
||||
async def global_search_endpoint(
|
||||
query: str,
|
||||
users_limit: int = 10,
|
||||
chats_limit: int = 10,
|
||||
messages_limit: int = 10,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
) -> GlobalSearchRead:
|
||||
return await global_search(
|
||||
db,
|
||||
user_id=current_user.id,
|
||||
query=query,
|
||||
users_limit=users_limit,
|
||||
chats_limit=chats_limit,
|
||||
messages_limit=messages_limit,
|
||||
)
|
||||
|
||||
12
app/search/schemas.py
Normal file
12
app/search/schemas.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.chats.schemas import ChatDiscoverRead
|
||||
from app.messages.schemas import MessageRead
|
||||
from app.users.schemas import UserSearchRead
|
||||
|
||||
|
||||
class GlobalSearchRead(BaseModel):
|
||||
users: list[UserSearchRead]
|
||||
chats: list[ChatDiscoverRead]
|
||||
messages: list[MessageRead]
|
||||
|
||||
82
app/search/service.py
Normal file
82
app/search/service.py
Normal file
@@ -0,0 +1,82 @@
|
||||
import asyncio
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.chats.models import Chat
|
||||
from app.chats import repository as chats_repository
|
||||
from app.chats.schemas import ChatDiscoverRead
|
||||
from app.chats.service import serialize_chat_for_user
|
||||
from app.messages.service import search_messages
|
||||
from app.search.schemas import GlobalSearchRead
|
||||
from app.users.service import search_users_by_username
|
||||
|
||||
|
||||
async def global_search(
|
||||
db: AsyncSession,
|
||||
*,
|
||||
user_id: int,
|
||||
query: str,
|
||||
users_limit: int = 10,
|
||||
chats_limit: int = 10,
|
||||
messages_limit: int = 10,
|
||||
) -> GlobalSearchRead:
|
||||
normalized = query.strip()
|
||||
if len(normalized.lstrip("@")) < 2:
|
||||
return GlobalSearchRead(users=[], chats=[], messages=[])
|
||||
|
||||
users_task = search_users_by_username(
|
||||
db,
|
||||
query=normalized,
|
||||
limit=max(1, min(users_limit, 50)),
|
||||
exclude_user_id=user_id,
|
||||
)
|
||||
messages_task = search_messages(
|
||||
db,
|
||||
user_id=user_id,
|
||||
query=normalized,
|
||||
chat_id=None,
|
||||
limit=max(1, min(messages_limit, 50)),
|
||||
)
|
||||
|
||||
users, messages = await asyncio.gather(users_task, messages_task)
|
||||
|
||||
# Combine own chats and discoverable public chats into one chat result list.
|
||||
own_chats = await chats_repository.list_user_chats(
|
||||
db,
|
||||
user_id=user_id,
|
||||
limit=max(1, min(chats_limit, 50)),
|
||||
query=normalized,
|
||||
)
|
||||
own_chat_ids = {chat.id for chat in own_chats}
|
||||
discovered_rows = await chats_repository.discover_public_chats(
|
||||
db,
|
||||
user_id=user_id,
|
||||
query=normalized,
|
||||
limit=max(1, min(chats_limit, 50)),
|
||||
)
|
||||
|
||||
merged_chats: list[tuple[Chat, bool]] = [(chat, True) for chat in own_chats]
|
||||
for chat, is_member in discovered_rows:
|
||||
if chat.id in own_chat_ids:
|
||||
continue
|
||||
merged_chats.append((chat, is_member))
|
||||
if len(merged_chats) >= max(1, min(chats_limit, 50)):
|
||||
break
|
||||
|
||||
chats: list[ChatDiscoverRead] = []
|
||||
for chat, is_member in merged_chats:
|
||||
serialized = await serialize_chat_for_user(db, user_id=user_id, chat=chat)
|
||||
chats.append(
|
||||
ChatDiscoverRead.model_validate(
|
||||
{
|
||||
**serialized.model_dump(),
|
||||
"is_member": bool(is_member),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
return GlobalSearchRead(
|
||||
users=users,
|
||||
chats=chats,
|
||||
messages=messages,
|
||||
)
|
||||
Reference in New Issue
Block a user