feat: add reply/forward/pin message flow across backend and web
Some checks failed
CI / test (push) Failing after 24s

- 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
This commit is contained in:
2026-03-08 00:28:43 +03:00
parent 4d704fc279
commit e1d0375392
18 changed files with 287 additions and 29 deletions

View File

@@ -6,12 +6,16 @@ from app.chats.service import ensure_chat_membership
from app.messages import repository
from app.messages.models import Message
from app.messages.spam_guard import enforce_message_spam_policy
from app.messages.schemas import MessageCreateRequest, MessageStatusUpdateRequest, MessageUpdateRequest
from app.messages.schemas import MessageCreateRequest, MessageForwardRequest, MessageStatusUpdateRequest, MessageUpdateRequest
from app.notifications.service import dispatch_message_notifications
async def create_chat_message(db: AsyncSession, *, sender_id: int, payload: MessageCreateRequest) -> Message:
await ensure_chat_membership(db, chat_id=payload.chat_id, user_id=sender_id)
if payload.reply_to_message_id is not None:
reply_to = await repository.get_message_by_id(db, payload.reply_to_message_id)
if not reply_to or reply_to.chat_id != payload.chat_id:
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="Invalid reply target")
if payload.client_message_id:
existing = await repository.get_message_by_client_message_id(
db,
@@ -30,6 +34,8 @@ async def create_chat_message(db: AsyncSession, *, sender_id: int, payload: Mess
db,
chat_id=payload.chat_id,
sender_id=sender_id,
reply_to_message_id=payload.reply_to_message_id,
forwarded_from_message_id=None,
message_type=payload.type,
text=payload.text,
)
@@ -167,3 +173,29 @@ async def mark_message_status(
"last_delivered_message_id": receipt.last_delivered_message_id or 0,
"last_read_message_id": receipt.last_read_message_id or 0,
}
async def forward_message(
db: AsyncSession,
*,
source_message_id: int,
sender_id: int,
payload: MessageForwardRequest,
) -> Message:
source = await repository.get_message_by_id(db, source_message_id)
if not source:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Source message not found")
await ensure_chat_membership(db, chat_id=source.chat_id, user_id=sender_id)
await ensure_chat_membership(db, chat_id=payload.target_chat_id, user_id=sender_id)
forwarded = await repository.create_message(
db,
chat_id=payload.target_chat_id,
sender_id=sender_id,
reply_to_message_id=None,
forwarded_from_message_id=source.id,
message_type=source.type,
text=source.text,
)
await db.commit()
await db.refresh(forwarded)
return forwarded