feat: add message reliability foundation
All checks were successful
CI / test (push) Successful in 23s
All checks were successful
CI / test (push) Successful in 23s
- implement idempotent message creation via client_message_id - add persistent delivered/read receipts - expose /messages/status and wire websocket receipt events - update web client to send client ids and auto-ack delivered/read
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.messages.models import Message, MessageType
|
||||
from app.messages.models import Message, MessageIdempotencyKey, MessageReceipt, MessageType
|
||||
|
||||
|
||||
async def create_message(
|
||||
@@ -18,6 +18,45 @@ async def create_message(
|
||||
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()
|
||||
@@ -39,3 +78,32 @@ async def list_chat_messages(
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user