Files
Messenger/app/search/service.py
benya bc483afd78
Some checks failed
CI / test (push) Failing after 24s
feat(search): add unified global search for users/chats/messages
2026-03-08 09:41:20 +03:00

83 lines
2.4 KiB
Python

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,
)