from fastapi import APIRouter, Depends, status from sqlalchemy.ext.asyncio import AsyncSession from app.auth.service import get_current_user from app.chats.schemas import ( ChatBanRead, ChatCreateRequest, ChatDetailRead, ChatDiscoverRead, ChatDeleteRequest, ChatInviteLinkRead, ChatJoinByInviteRequest, ChatMemberAddRequest, ChatMemberRead, ChatMemberRoleUpdateRequest, ChatNotificationSettingsRead, ChatNotificationSettingsUpdate, ChatPinMessageRequest, ChatProfileUpdateRequest, ChatRead, ChatTitleUpdateRequest, ) from app.chats import repository as chats_repository from app.chats.models import ChatMemberRole, ChatType from app.chats.service import ( add_chat_member_for_user, ban_chat_member_for_user, create_chat_for_user, create_chat_invite_link_for_user, clear_chat_for_user, delete_chat_for_user, discover_public_chats_for_user, ensure_saved_messages_chat, get_chat_for_user, get_chat_notification_settings_for_user, get_chats_for_user, join_public_chat_for_user, join_chat_by_invite_for_user, leave_chat_for_user, pin_chat_message_for_user, remove_chat_member_for_user, list_chat_bans_for_user, serialize_chat_for_user, serialize_chats_for_user, set_chat_archived_for_user, set_chat_pinned_for_user, unban_chat_member_for_user, update_chat_member_role_for_user, update_chat_notification_settings_for_user, update_chat_profile_for_user, update_chat_title_for_user, ) from app.database.session import get_db from app.realtime.service import realtime_gateway from app.users.models import User router = APIRouter(prefix="/chats", tags=["chats"]) @router.get("", response_model=list[ChatRead]) async def list_chats( limit: int = 50, before_id: int | None = None, query: str | None = None, archived: bool = False, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ) -> list[ChatRead]: chats = await get_chats_for_user( db, user_id=current_user.id, limit=limit, before_id=before_id, query=query, archived=archived, ) return await serialize_chats_for_user(db, user_id=current_user.id, chats=chats) @router.get("/saved", response_model=ChatRead) async def get_saved_messages_chat( db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ) -> ChatRead: chat = await ensure_saved_messages_chat(db, user_id=current_user.id) return await serialize_chat_for_user(db, user_id=current_user.id, chat=chat) @router.get("/discover", response_model=list[ChatDiscoverRead]) async def discover_chats( query: str | None = None, limit: int = 30, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ) -> list[ChatDiscoverRead]: return await discover_public_chats_for_user(db, user_id=current_user.id, query=query, limit=limit) @router.post("", response_model=ChatRead) async def create_chat( payload: ChatCreateRequest, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ) -> ChatRead: chat = await create_chat_for_user(db, creator_id=current_user.id, payload=payload) member_user_ids = await chats_repository.list_chat_member_user_ids(db, chat_id=chat.id) for member_user_id in member_user_ids: realtime_gateway.add_chat_subscription(chat_id=chat.id, user_id=member_user_id) await realtime_gateway.publish_chat_updated(chat_id=chat.id) return await serialize_chat_for_user(db, user_id=current_user.id, chat=chat) @router.post("/{chat_id}/join", response_model=ChatRead) async def join_chat( chat_id: int, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ) -> ChatRead: chat = await join_public_chat_for_user(db, chat_id=chat_id, user_id=current_user.id) realtime_gateway.add_chat_subscription(chat_id=chat.id, user_id=current_user.id) await realtime_gateway.publish_chat_updated(chat_id=chat.id) return await serialize_chat_for_user(db, user_id=current_user.id, chat=chat) @router.get("/{chat_id}", response_model=ChatDetailRead) async def get_chat( chat_id: int, db: AsyncSession = Depends(get_db), 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) base = await serialize_chat_for_user(db, user_id=current_user.id, chat=chat) return ChatDetailRead.model_validate( { **base.model_dump(), "members": members, } ) @router.patch("/{chat_id}/title", response_model=ChatRead) async def update_chat_title( chat_id: int, payload: ChatTitleUpdateRequest, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ) -> ChatRead: chat = await update_chat_title_for_user(db, chat_id=chat_id, user_id=current_user.id, payload=payload) await realtime_gateway.publish_chat_updated(chat_id=chat.id) return await serialize_chat_for_user(db, user_id=current_user.id, chat=chat) @router.patch("/{chat_id}/profile", response_model=ChatRead) async def update_chat_profile( chat_id: int, payload: ChatProfileUpdateRequest, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ) -> ChatRead: chat = await update_chat_profile_for_user(db, chat_id=chat_id, user_id=current_user.id, payload=payload) await realtime_gateway.publish_chat_updated(chat_id=chat.id) return await serialize_chat_for_user(db, user_id=current_user.id, chat=chat) @router.get("/{chat_id}/members", response_model=list[ChatMemberRead]) async def list_chat_members( chat_id: int, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ) -> list[ChatMemberRead]: _chat, members = await get_chat_for_user(db, chat_id=chat_id, user_id=current_user.id) return members @router.post("/{chat_id}/members", response_model=ChatMemberRead, status_code=status.HTTP_201_CREATED) async def add_chat_member( chat_id: int, payload: ChatMemberAddRequest, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ) -> ChatMemberRead: member = await add_chat_member_for_user(db, chat_id=chat_id, actor_user_id=current_user.id, target_user_id=payload.user_id) realtime_gateway.add_chat_subscription(chat_id=chat_id, user_id=payload.user_id) await realtime_gateway.publish_chat_updated(chat_id=chat_id) return member @router.patch("/{chat_id}/members/{user_id}/role", response_model=ChatMemberRead) async def update_chat_member_role( chat_id: int, user_id: int, payload: ChatMemberRoleUpdateRequest, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ) -> ChatMemberRead: member = await update_chat_member_role_for_user( db, chat_id=chat_id, actor_user_id=current_user.id, target_user_id=user_id, role=payload.role, ) await realtime_gateway.publish_chat_updated(chat_id=chat_id) return member @router.delete("/{chat_id}/members/{user_id}", status_code=status.HTTP_204_NO_CONTENT) async def remove_chat_member( chat_id: int, user_id: int, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ) -> None: await remove_chat_member_for_user(db, chat_id=chat_id, actor_user_id=current_user.id, target_user_id=user_id) realtime_gateway.remove_chat_subscription(chat_id=chat_id, user_id=user_id) await realtime_gateway.publish_chat_updated(chat_id=chat_id) @router.post("/{chat_id}/bans/{user_id}", status_code=status.HTTP_204_NO_CONTENT) async def ban_chat_member( chat_id: int, user_id: int, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ) -> None: await ban_chat_member_for_user(db, chat_id=chat_id, actor_user_id=current_user.id, target_user_id=user_id) realtime_gateway.remove_chat_subscription(chat_id=chat_id, user_id=user_id) await realtime_gateway.publish_chat_updated(chat_id=chat_id) @router.get("/{chat_id}/bans", response_model=list[ChatBanRead]) async def list_chat_bans( chat_id: int, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ) -> list[ChatBanRead]: return await list_chat_bans_for_user(db, chat_id=chat_id, actor_user_id=current_user.id) @router.delete("/{chat_id}/bans/{user_id}", status_code=status.HTTP_204_NO_CONTENT) async def unban_chat_member( chat_id: int, user_id: int, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ) -> None: await unban_chat_member_for_user(db, chat_id=chat_id, actor_user_id=current_user.id, target_user_id=user_id) await realtime_gateway.publish_chat_updated(chat_id=chat_id) @router.post("/{chat_id}/leave", status_code=status.HTTP_204_NO_CONTENT) async def leave_chat( chat_id: int, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ) -> None: await leave_chat_for_user(db, chat_id=chat_id, user_id=current_user.id) realtime_gateway.remove_chat_subscription(chat_id=chat_id, user_id=current_user.id) await realtime_gateway.publish_chat_updated(chat_id=chat_id) @router.delete("/{chat_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_chat( chat_id: int, for_all: bool = False, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ) -> None: chat_before_delete = await chats_repository.get_chat_by_id(db, chat_id) membership_before_delete = ( await chats_repository.get_chat_member(db, chat_id=chat_id, user_id=current_user.id) if chat_before_delete else None ) delete_for_all = bool( chat_before_delete and ( (for_all and not chat_before_delete.is_saved) or ( chat_before_delete.type == ChatType.CHANNEL and not chat_before_delete.is_saved and membership_before_delete is not None and membership_before_delete.role in {ChatMemberRole.OWNER, ChatMemberRole.ADMIN} ) ) ) await delete_chat_for_user(db, chat_id=chat_id, user_id=current_user.id, payload=ChatDeleteRequest(for_all=for_all)) if chat_before_delete and not chat_before_delete.is_saved: realtime_gateway.remove_chat_subscription(chat_id=chat_id, user_id=current_user.id) if delete_for_all: await realtime_gateway.publish_chat_deleted(chat_id=chat_id) else: await realtime_gateway.publish_chat_updated(chat_id=chat_id) @router.post("/{chat_id}/clear", status_code=status.HTTP_204_NO_CONTENT) async def clear_chat( chat_id: int, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ) -> None: await clear_chat_for_user(db, chat_id=chat_id, user_id=current_user.id) await realtime_gateway.publish_chat_updated(chat_id=chat_id) @router.post("/{chat_id}/pin", response_model=ChatRead) async def pin_chat_message( chat_id: int, payload: ChatPinMessageRequest, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ) -> ChatRead: chat = await pin_chat_message_for_user(db, chat_id=chat_id, user_id=current_user.id, payload=payload) await realtime_gateway.publish_chat_updated(chat_id=chat.id) return await serialize_chat_for_user(db, user_id=current_user.id, chat=chat) @router.get("/{chat_id}/notifications", response_model=ChatNotificationSettingsRead) async def get_chat_notifications( chat_id: int, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ) -> ChatNotificationSettingsRead: return await get_chat_notification_settings_for_user(db, chat_id=chat_id, user_id=current_user.id) @router.put("/{chat_id}/notifications", response_model=ChatNotificationSettingsRead) async def update_chat_notifications( chat_id: int, payload: ChatNotificationSettingsUpdate, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ) -> ChatNotificationSettingsRead: settings = await update_chat_notification_settings_for_user( db, chat_id=chat_id, user_id=current_user.id, payload=payload, ) await realtime_gateway.publish_chat_updated(chat_id=chat_id) return settings @router.post("/{chat_id}/archive", response_model=ChatRead) async def archive_chat( chat_id: int, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ) -> ChatRead: chat = await set_chat_archived_for_user(db, chat_id=chat_id, user_id=current_user.id, archived=True) await realtime_gateway.publish_chat_updated(chat_id=chat_id) return await serialize_chat_for_user(db, user_id=current_user.id, chat=chat) @router.post("/{chat_id}/unarchive", response_model=ChatRead) async def unarchive_chat( chat_id: int, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ) -> ChatRead: chat = await set_chat_archived_for_user(db, chat_id=chat_id, user_id=current_user.id, archived=False) await realtime_gateway.publish_chat_updated(chat_id=chat_id) 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) await realtime_gateway.publish_chat_updated(chat_id=chat_id) 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) await realtime_gateway.publish_chat_updated(chat_id=chat_id) return await serialize_chat_for_user(db, user_id=current_user.id, chat=chat) @router.post("/{chat_id}/invite-link", response_model=ChatInviteLinkRead) async def create_invite_link( chat_id: int, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ) -> ChatInviteLinkRead: return await create_chat_invite_link_for_user(db, chat_id=chat_id, user_id=current_user.id) @router.post("/join-by-invite", response_model=ChatRead) async def join_by_invite( payload: ChatJoinByInviteRequest, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ) -> ChatRead: chat = await join_chat_by_invite_for_user(db, user_id=current_user.id, payload=payload) realtime_gateway.add_chat_subscription(chat_id=chat.id, user_id=current_user.id) await realtime_gateway.publish_chat_updated(chat_id=chat.id) return await serialize_chat_for_user(db, user_id=current_user.id, chat=chat)