diff --git a/app/chats/router.py b/app/chats/router.py index ec75edb..f8192ac 100644 --- a/app/chats/router.py +++ b/app/chats/router.py @@ -96,13 +96,12 @@ async def get_chat( current_user: User = Depends(get_current_user), ) -> ChatDetailRead: chat, members = await get_chat_for_user(db, chat_id=chat_id, user_id=current_user.id) - return ChatDetailRead( - id=chat.id, - type=chat.type, - title=chat.title, - pinned_message_id=chat.pinned_message_id, - created_at=chat.created_at, - members=members, + base = await serialize_chat_for_user(db, user_id=current_user.id, chat=chat) + return ChatDetailRead.model_validate( + { + **base.model_dump(), + "members": members, + } ) diff --git a/app/chats/schemas.py b/app/chats/schemas.py index c01b9c9..4e90326 100644 --- a/app/chats/schemas.py +++ b/app/chats/schemas.py @@ -26,6 +26,7 @@ class ChatRead(BaseModel): counterpart_username: str | None = None counterpart_is_online: bool | None = None counterpart_last_seen_at: datetime | None = None + my_role: ChatMemberRole | None = None created_at: datetime diff --git a/app/chats/service.py b/app/chats/service.py index 1d70fb6..4c3fe30 100644 --- a/app/chats/service.py +++ b/app/chats/service.py @@ -18,6 +18,10 @@ from app.users.repository import get_user_by_id async def serialize_chat_for_user(db: AsyncSession, *, user_id: int, chat: Chat) -> ChatRead: display_title = chat.title + my_role = None + membership = await repository.get_chat_member(db, chat_id=chat.id, user_id=user_id) + if membership: + my_role = membership.role members_count: int | None = None online_count: int | None = None subscribers_count: int | None = None @@ -70,6 +74,7 @@ async def serialize_chat_for_user(db: AsyncSession, *, user_id: int, chat: Chat) "counterpart_username": counterpart_username, "counterpart_is_online": counterpart_is_online, "counterpart_last_seen_at": counterpart_last_seen_at, + "my_role": my_role, "created_at": chat.created_at, } ) diff --git a/app/messages/service.py b/app/messages/service.py index bc21693..ff2e28e 100644 --- a/app/messages/service.py +++ b/app/messages/service.py @@ -14,6 +14,14 @@ 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) + chat = await chats_repository.get_chat_by_id(db, payload.chat_id) + if not chat: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Chat not found") + membership = await chats_repository.get_chat_member(db, chat_id=payload.chat_id, user_id=sender_id) + if not membership: + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="You are not a member of this chat") + if chat.type == ChatType.CHANNEL and membership.role == ChatMemberRole.MEMBER: + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Only admins can post in channels") 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: @@ -229,6 +237,14 @@ async def forward_message( 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) + target_chat = await chats_repository.get_chat_by_id(db, payload.target_chat_id) + if not target_chat: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Chat not found") + target_membership = await chats_repository.get_chat_member(db, chat_id=payload.target_chat_id, user_id=sender_id) + if not target_membership: + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="You are not a member of this chat") + if target_chat.type == ChatType.CHANNEL and target_membership.role == ChatMemberRole.MEMBER: + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Only admins can post in channels") forwarded = await repository.create_message( db, chat_id=payload.target_chat_id, diff --git a/web/src/chat/types.ts b/web/src/chat/types.ts index 9ad10bc..4049fc0 100644 --- a/web/src/chat/types.ts +++ b/web/src/chat/types.ts @@ -21,6 +21,7 @@ export interface Chat { counterpart_username?: string | null; counterpart_is_online?: boolean | null; counterpart_last_seen_at?: string | null; + my_role?: ChatMemberRole | null; created_at: string; } diff --git a/web/src/components/ChatInfoPanel.tsx b/web/src/components/ChatInfoPanel.tsx index 422901d..20ee345 100644 --- a/web/src/components/ChatInfoPanel.tsx +++ b/web/src/components/ChatInfoPanel.tsx @@ -36,6 +36,7 @@ export function ChatInfoPanel({ chatId, open, onClose }: Props) { const myRole = useMemo(() => members.find((m) => m.user_id === me?.id)?.role, [members, me?.id]); const isGroupLike = chat?.type === "group" || chat?.type === "channel"; + const showMembersSection = Boolean(chat && isGroupLike && !chat.is_saved); const canManageMembers = Boolean(isGroupLike && (myRole === "owner" || myRole === "admin")); const canChangeRoles = Boolean(isGroupLike && myRole === "owner"); @@ -139,56 +140,74 @@ export function ChatInfoPanel({ chatId, open, onClose }: Props) { {chat.description ?
{chat.description}
: null} -Members ({members.length})
-{user?.name || `user #${member.user_id}`}
-@{user?.username || "unknown"}
-Saved Messages is your personal cloud chat.
+ ) : chat.type === "private" ? ( ++ {chat.counterpart_is_online + ? "User is online" + : chat.counterpart_last_seen_at + ? `Last seen ${formatLastSeen(chat.counterpart_last_seen_at)}` + : "User is offline"} +
+ ) : ( +No extra information.
+ )} +Add member
) : null} - {chat.type === "group" || chat.type === "channel" ? ( + {showMembersSection && (chat.type === "group" || chat.type === "channel") ? (