Files
Messenger/app/messages/repository.py
benya e1d0375392
Some checks failed
CI / test (push) Failing after 24s
feat: add reply/forward/pin message flow across backend and web
- add reply_to/forwarded_from message fields and chat pinned_message field

- add forward and pin APIs plus reply support in message create

- wire web actions: Reply, Fwd, Pin and reply composer state

- fix spam policy bug: allow repeated identical messages, keep rate limiting
2026-03-08 00:28:43 +03:00

145 lines
3.8 KiB
Python

from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.chats.models import ChatMember
from app.messages.models import Message, MessageIdempotencyKey, MessageReceipt, MessageType
async def create_message(
db: AsyncSession,
*,
chat_id: int,
sender_id: int,
reply_to_message_id: int | None,
forwarded_from_message_id: int | None,
message_type: MessageType,
text: str | None,
) -> Message:
message = Message(
chat_id=chat_id,
sender_id=sender_id,
reply_to_message_id=reply_to_message_id,
forwarded_from_message_id=forwarded_from_message_id,
type=message_type,
text=text,
)
db.add(message)
await db.flush()
return message
async def get_message_by_client_message_id(
db: AsyncSession,
*,
chat_id: int,
sender_id: int,
client_message_id: str,
) -> Message | None:
result = await db.execute(
select(Message)
.join(MessageIdempotencyKey, MessageIdempotencyKey.message_id == Message.id)
.where(
MessageIdempotencyKey.chat_id == chat_id,
MessageIdempotencyKey.sender_id == sender_id,
MessageIdempotencyKey.client_message_id == client_message_id,
)
.limit(1)
)
return result.scalar_one_or_none()
async def create_message_idempotency_key(
db: AsyncSession,
*,
chat_id: int,
sender_id: int,
client_message_id: str,
message_id: int,
) -> MessageIdempotencyKey:
key = MessageIdempotencyKey(
chat_id=chat_id,
sender_id=sender_id,
client_message_id=client_message_id,
message_id=message_id,
)
db.add(key)
await db.flush()
return key
async def get_message_by_id(db: AsyncSession, message_id: int) -> Message | None:
result = await db.execute(select(Message).where(Message.id == message_id))
return result.scalar_one_or_none()
async def list_chat_messages(
db: AsyncSession,
chat_id: int,
*,
limit: int = 50,
before_id: int | None = None,
) -> list[Message]:
query = select(Message).where(Message.chat_id == chat_id)
if before_id is not None:
query = query.where(Message.id < before_id)
result = await db.execute(query.order_by(Message.id.desc()).limit(limit))
return list(result.scalars().all())
async def search_messages(
db: AsyncSession,
*,
user_id: int,
query: str,
chat_id: int | None = None,
limit: int = 50,
) -> list[Message]:
stmt = (
select(Message)
.join(ChatMember, ChatMember.chat_id == Message.chat_id)
.where(
ChatMember.user_id == user_id,
Message.text.is_not(None),
Message.text.ilike(f"%{query.strip()}%"),
)
.order_by(Message.id.desc())
.limit(limit)
)
if chat_id is not None:
stmt = stmt.where(Message.chat_id == chat_id)
result = await db.execute(stmt)
return list(result.scalars().all())
async def delete_message(db: AsyncSession, message: Message) -> None:
await db.delete(message)
async def get_message_receipt(db: AsyncSession, *, chat_id: int, user_id: int) -> MessageReceipt | None:
result = await db.execute(
select(MessageReceipt).where(
MessageReceipt.chat_id == chat_id,
MessageReceipt.user_id == user_id,
)
)
return result.scalar_one_or_none()
async def create_message_receipt(
db: AsyncSession,
*,
chat_id: int,
user_id: int,
last_delivered_message_id: int | None,
last_read_message_id: int | None,
) -> MessageReceipt:
receipt = MessageReceipt(
chat_id=chat_id,
user_id=user_id,
last_delivered_message_id=last_delivered_message_id,
last_read_message_id=last_read_message_id,
)
db.add(receipt)
await db.flush()
return receipt