Private
Public Access
A DM is a Room with a new is_dm flag, not a separate model -- reuses all the membership/message/WS plumbing Room already has instead of duplicating it. The room's `name` (still required + globally unique) is an internal, never-displayed token derived deterministically from the two participants' sorted user IDs (dm_room_name), which makes find-or-create a single indexed lookup and gets free race-condition safety from the existing unique constraint -- a concurrent double- start from both people just hits the same IntegrityError->retry-as- lookup path create_room already established. Both participants get the plain 'member' role (no owner/admin distinction makes sense for a 1:1 DM), which incidentally reuses every existing role gate to block add-member, room-settings edits, and join-via-browse on a DM for free. update_room also gets an explicit is_dm guard independent of that, since renaming a DM isn't just a privacy concern -- it would silently corrupt the find-or-create invariant. DMs are excluded from both Browse Rooms and the admin portal's room listing (fully private, per scope). GET /api/rooms/mine precomputes each DM's other participant (name, avatar, presence) as dm_partner in one batched query, so the sidebar can render a DM row without a fetch per row. Frontend: a new "Direct Messages" sidebar section (searchable by partner name, not the internal room name), clicking someone in the People list starts or resumes a DM, and the chat header/composer/RoomInfoPanel all render the partner's identity instead of a room name where it's a DM. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
31 lines
1.3 KiB
Python
31 lines
1.3 KiB
Python
import uuid
|
|
from datetime import datetime
|
|
|
|
from sqlalchemy import Boolean, DateTime, ForeignKey, String, Text, func
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
|
|
from app.models.base import Base
|
|
|
|
|
|
class Room(Base):
|
|
__tablename__ = "rooms"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4)
|
|
name: Mapped[str] = mapped_column(String(100), unique=True, index=True, nullable=False)
|
|
description: Mapped[str | None] = mapped_column(Text)
|
|
is_private: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
|
is_archived: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
|
# #52: a DM is a Room whose `name` is an internal, never-displayed
|
|
# deterministic token (see room_service.dm_room_name) rather than a
|
|
# user-chosen name -- see that function's docstring for the scheme.
|
|
is_dm: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
|
owner_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("users.id"), nullable=False)
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), server_default=func.now(), nullable=False
|
|
)
|
|
|
|
owner = relationship("User")
|
|
memberships = relationship(
|
|
"RoomMembership", back_populates="room", cascade="all, delete-orphan"
|
|
)
|