feat: improve transcript listing with room_id (#496)

Added a new field in transcript for room_id, and set room_id/meeting_id
in a transcript now. Use this field to list the transcripts. URL is now
very fast.
This commit is contained in:
2025-07-17 15:43:36 -06:00
committed by GitHub
parent 0eb670ca19
commit 033bd4bc48
3 changed files with 87 additions and 11 deletions

View File

@@ -0,0 +1,59 @@
"""Add room_id to transcript
Revision ID: d7fbb74b673b
Revises: a9c9c229ee36
Create Date: 2025-07-17 12:00:00.000000
"""
from typing import Sequence, Union
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision: str = "d7fbb74b673b"
down_revision: Union[str, None] = "a9c9c229ee36"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# Add room_id column to transcript table
op.add_column("transcript", sa.Column("room_id", sa.String(), nullable=True))
# Add index for room_id for better query performance
op.create_index("idx_transcript_room_id", "transcript", ["room_id"])
# Populate room_id for existing ROOM-type transcripts
# This joins through recording -> meeting -> room to get the room_id
op.execute("""
UPDATE transcript AS t
SET room_id = r.id
FROM recording rec
JOIN meeting m ON rec.meeting_id = m.id
JOIN room r ON m.room_id = r.id
WHERE t.recording_id = rec.id
AND t.source_kind = 'room'
AND t.room_id IS NULL
""")
# Fix missing meeting_id for ROOM-type transcripts
# The meeting_id field exists but was never populated
op.execute("""
UPDATE transcript AS t
SET meeting_id = rec.meeting_id
FROM recording rec
WHERE t.recording_id = rec.id
AND t.source_kind = 'room'
AND t.meeting_id IS NULL
AND rec.meeting_id IS NOT NULL
""")
def downgrade() -> None:
# Drop the index first
op.drop_index("idx_transcript_room_id", "transcript")
# Drop the room_id column
op.drop_column("transcript", "room_id")

View File

@@ -74,10 +74,12 @@ transcripts = sqlalchemy.Table(
# the main "audio deleted" is the presence of the audio itself / consents not-given # the main "audio deleted" is the presence of the audio itself / consents not-given
# same field could've been in recording/meeting, and it's maybe even ok to dupe it at need # same field could've been in recording/meeting, and it's maybe even ok to dupe it at need
sqlalchemy.Column("audio_deleted", sqlalchemy.Boolean), sqlalchemy.Column("audio_deleted", sqlalchemy.Boolean),
sqlalchemy.Column("room_id", sqlalchemy.String),
sqlalchemy.Index("idx_transcript_recording_id", "recording_id"), sqlalchemy.Index("idx_transcript_recording_id", "recording_id"),
sqlalchemy.Index("idx_transcript_user_id", "user_id"), sqlalchemy.Index("idx_transcript_user_id", "user_id"),
sqlalchemy.Index("idx_transcript_created_at", "created_at"), sqlalchemy.Index("idx_transcript_created_at", "created_at"),
sqlalchemy.Index("idx_transcript_user_id_recording_id", "user_id", "recording_id"), sqlalchemy.Index("idx_transcript_user_id_recording_id", "user_id", "recording_id"),
sqlalchemy.Index("idx_transcript_room_id", "room_id"),
) )
@@ -167,6 +169,7 @@ class Transcript(BaseModel):
zulip_message_id: int | None = None zulip_message_id: int | None = None
source_kind: SourceKind source_kind: SourceKind
audio_deleted: bool | None = None audio_deleted: bool | None = None
room_id: str | None = None
@field_serializer("created_at", when_used="json") @field_serializer("created_at", when_used="json")
def serialize_datetime(self, dt: datetime) -> str: def serialize_datetime(self, dt: datetime) -> str:
@@ -331,17 +334,10 @@ class TranscriptController:
- `room_id`: filter transcripts by room ID - `room_id`: filter transcripts by room ID
- `search_term`: filter transcripts by search term - `search_term`: filter transcripts by search term
""" """
from reflector.db.meetings import meetings
from reflector.db.recordings import recordings
from reflector.db.rooms import rooms from reflector.db.rooms import rooms
query = ( query = transcripts.select().join(
transcripts.select() rooms, transcripts.c.room_id == rooms.c.id, isouter=True
.join(
recordings, transcripts.c.recording_id == recordings.c.id, isouter=True
)
.join(meetings, recordings.c.meeting_id == meetings.c.id, isouter=True)
.join(rooms, meetings.c.room_id == rooms.c.id, isouter=True)
) )
if user_id: if user_id:
@@ -355,7 +351,7 @@ class TranscriptController:
query = query.where(transcripts.c.source_kind == source_kind) query = query.where(transcripts.c.source_kind == source_kind)
if room_id: if room_id:
query = query.where(rooms.c.id == room_id) query = query.where(transcripts.c.room_id == room_id)
if search_term: if search_term:
query = query.where(transcripts.c.title.ilike(f"%{search_term}%")) query = query.where(transcripts.c.title.ilike(f"%{search_term}%"))
@@ -368,7 +364,6 @@ class TranscriptController:
query = query.with_only_columns( query = query.with_only_columns(
transcript_columns transcript_columns
+ [ + [
rooms.c.id.label("room_id"),
rooms.c.name.label("room_name"), rooms.c.name.label("room_name"),
] ]
) )
@@ -419,6 +414,22 @@ class TranscriptController:
return None return None
return Transcript(**result) return Transcript(**result)
async def get_by_room_id(self, room_id: str, **kwargs) -> list[Transcript]:
"""
Get transcripts by room_id (direct access without joins)
"""
query = transcripts.select().where(transcripts.c.room_id == room_id)
if "user_id" in kwargs:
query = query.where(transcripts.c.user_id == kwargs["user_id"])
if "order_by" in kwargs:
order_by = kwargs["order_by"]
field = getattr(transcripts.c, order_by[1:])
if order_by.startswith("-"):
field = field.desc()
query = query.order_by(field)
results = await database.fetch_all(query)
return [Transcript(**result) for result in results]
async def get_by_id_for_http( async def get_by_id_for_http(
self, self,
transcript_id: str, transcript_id: str,
@@ -469,6 +480,8 @@ class TranscriptController:
user_id: str | None = None, user_id: str | None = None,
recording_id: str | None = None, recording_id: str | None = None,
share_mode: str = "private", share_mode: str = "private",
meeting_id: str | None = None,
room_id: str | None = None,
): ):
""" """
Add a new transcript Add a new transcript
@@ -481,6 +494,8 @@ class TranscriptController:
user_id=user_id, user_id=user_id,
recording_id=recording_id, recording_id=recording_id,
share_mode=share_mode, share_mode=share_mode,
meeting_id=meeting_id,
room_id=room_id,
) )
query = transcripts.insert().values(**transcript.model_dump()) query = transcripts.insert().values(**transcript.model_dump())
await database.execute(query) await database.execute(query)

View File

@@ -101,6 +101,8 @@ async def process_recording(bucket_name: str, object_key: str):
user_id=room.user_id, user_id=room.user_id,
recording_id=recording.id, recording_id=recording.id,
share_mode="public", share_mode="public",
meeting_id=meeting.id,
room_id=room.id,
) )
_, extension = os.path.splitext(object_key) _, extension = os.path.splitext(object_key)