feat(chats): add per-user pinned chats and pinned sorting
This commit is contained in:
@@ -85,6 +85,8 @@ class ChatUserSetting(Base):
|
||||
chat_id: Mapped[int] = mapped_column(ForeignKey("chats.id", ondelete="CASCADE"), nullable=False, index=True)
|
||||
user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True)
|
||||
archived: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False, server_default="false")
|
||||
pinned: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False, server_default="false")
|
||||
pinned_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
server_default=func.now(),
|
||||
|
||||
@@ -70,7 +70,11 @@ def _user_chats_query(user_id: int, query: str | None = None) -> Select[tuple[Ch
|
||||
Chat.type.cast(String).ilike(q),
|
||||
)
|
||||
)
|
||||
return stmt.order_by(Chat.id.desc())
|
||||
return stmt.order_by(
|
||||
func.coalesce(ChatUserSetting.pinned, False).desc(),
|
||||
ChatUserSetting.pinned_at.desc().nullslast(),
|
||||
Chat.id.desc(),
|
||||
)
|
||||
|
||||
|
||||
async def list_user_chats(
|
||||
@@ -103,7 +107,11 @@ async def list_archived_user_chats(
|
||||
(ChatUserSetting.chat_id == Chat.id) & (ChatUserSetting.user_id == user_id),
|
||||
)
|
||||
.where(ChatMember.user_id == user_id, ChatUserSetting.archived.is_(True))
|
||||
.order_by(Chat.id.desc())
|
||||
.order_by(
|
||||
func.coalesce(ChatUserSetting.pinned, False).desc(),
|
||||
ChatUserSetting.pinned_at.desc().nullslast(),
|
||||
Chat.id.desc(),
|
||||
)
|
||||
.limit(limit)
|
||||
)
|
||||
if before_id is not None:
|
||||
@@ -287,3 +295,28 @@ async def upsert_chat_archived_setting(
|
||||
db.add(setting)
|
||||
await db.flush()
|
||||
return setting
|
||||
|
||||
|
||||
async def upsert_chat_pinned_setting(
|
||||
db: AsyncSession,
|
||||
*,
|
||||
chat_id: int,
|
||||
user_id: int,
|
||||
pinned: bool,
|
||||
) -> ChatUserSetting:
|
||||
setting = await get_chat_user_setting(db, chat_id=chat_id, user_id=user_id)
|
||||
if setting:
|
||||
setting.pinned = pinned
|
||||
setting.pinned_at = func.now() if pinned else None
|
||||
await db.flush()
|
||||
return setting
|
||||
setting = ChatUserSetting(
|
||||
chat_id=chat_id,
|
||||
user_id=user_id,
|
||||
archived=False,
|
||||
pinned=pinned,
|
||||
pinned_at=func.now() if pinned else None,
|
||||
)
|
||||
db.add(setting)
|
||||
await db.flush()
|
||||
return setting
|
||||
|
||||
@@ -33,6 +33,7 @@ from app.chats.service import (
|
||||
serialize_chat_for_user,
|
||||
serialize_chats_for_user,
|
||||
set_chat_archived_for_user,
|
||||
set_chat_pinned_for_user,
|
||||
update_chat_member_role_for_user,
|
||||
update_chat_notification_settings_for_user,
|
||||
update_chat_title_for_user,
|
||||
@@ -257,3 +258,23 @@ async def unarchive_chat(
|
||||
) -> ChatRead:
|
||||
chat = await set_chat_archived_for_user(db, chat_id=chat_id, user_id=current_user.id, archived=False)
|
||||
return await serialize_chat_for_user(db, user_id=current_user.id, chat=chat)
|
||||
|
||||
|
||||
@router.post("/{chat_id}/pin-chat", response_model=ChatRead)
|
||||
async def pin_chat(
|
||||
chat_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
) -> ChatRead:
|
||||
chat = await set_chat_pinned_for_user(db, chat_id=chat_id, user_id=current_user.id, pinned=True)
|
||||
return await serialize_chat_for_user(db, user_id=current_user.id, chat=chat)
|
||||
|
||||
|
||||
@router.post("/{chat_id}/unpin-chat", response_model=ChatRead)
|
||||
async def unpin_chat(
|
||||
chat_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
) -> ChatRead:
|
||||
chat = await set_chat_pinned_for_user(db, chat_id=chat_id, user_id=current_user.id, pinned=False)
|
||||
return await serialize_chat_for_user(db, user_id=current_user.id, chat=chat)
|
||||
|
||||
@@ -18,6 +18,7 @@ class ChatRead(BaseModel):
|
||||
is_public: bool = False
|
||||
is_saved: bool = False
|
||||
archived: bool = False
|
||||
pinned: bool = False
|
||||
unread_count: int = 0
|
||||
pinned_message_id: int | None = None
|
||||
members_count: int | None = None
|
||||
|
||||
@@ -64,6 +64,7 @@ async def serialize_chat_for_user(db: AsyncSession, *, user_id: int, chat: Chat)
|
||||
unread_count = await repository.get_unread_count_for_chat(db, chat_id=chat.id, user_id=user_id)
|
||||
user_setting = await repository.get_chat_user_setting(db, chat_id=chat.id, user_id=user_id)
|
||||
archived = bool(user_setting and user_setting.archived)
|
||||
pinned = bool(user_setting and user_setting.pinned)
|
||||
|
||||
return ChatRead.model_validate(
|
||||
{
|
||||
@@ -77,6 +78,7 @@ async def serialize_chat_for_user(db: AsyncSession, *, user_id: int, chat: Chat)
|
||||
"is_public": chat.is_public,
|
||||
"is_saved": chat.is_saved,
|
||||
"archived": archived,
|
||||
"pinned": pinned,
|
||||
"unread_count": unread_count,
|
||||
"pinned_message_id": chat.pinned_message_id,
|
||||
"members_count": members_count,
|
||||
@@ -501,3 +503,17 @@ async def set_chat_archived_for_user(
|
||||
await db.commit()
|
||||
await db.refresh(chat)
|
||||
return chat
|
||||
|
||||
|
||||
async def set_chat_pinned_for_user(
|
||||
db: AsyncSession,
|
||||
*,
|
||||
chat_id: int,
|
||||
user_id: int,
|
||||
pinned: bool,
|
||||
) -> Chat:
|
||||
chat, _membership = await _get_chat_and_membership(db, chat_id=chat_id, user_id=user_id)
|
||||
await repository.upsert_chat_pinned_setting(db, chat_id=chat_id, user_id=user_id, pinned=pinned)
|
||||
await db.commit()
|
||||
await db.refresh(chat)
|
||||
return chat
|
||||
|
||||
Reference in New Issue
Block a user