Get all conversations for a project with message counts. Uses a subquery for message_count to avoid N+1 query problem.
(project_dir: Path, project_name: str)
| 153 | |
| 154 | |
| 155 | def get_conversations(project_dir: Path, project_name: str) -> list[dict]: |
| 156 | """Get all conversations for a project with message counts. |
| 157 | |
| 158 | Uses a subquery for message_count to avoid N+1 query problem. |
| 159 | """ |
| 160 | session = get_session(project_dir) |
| 161 | try: |
| 162 | # Subquery to count messages per conversation (avoids N+1 query) |
| 163 | message_count_subquery = ( |
| 164 | session.query( |
| 165 | ConversationMessage.conversation_id, |
| 166 | func.count(ConversationMessage.id).label("message_count") |
| 167 | ) |
| 168 | .group_by(ConversationMessage.conversation_id) |
| 169 | .subquery() |
| 170 | ) |
| 171 | |
| 172 | # Join conversation with message counts |
| 173 | conversations = ( |
| 174 | session.query( |
| 175 | Conversation, |
| 176 | func.coalesce(message_count_subquery.c.message_count, 0).label("message_count") |
| 177 | ) |
| 178 | .outerjoin( |
| 179 | message_count_subquery, |
| 180 | Conversation.id == message_count_subquery.c.conversation_id |
| 181 | ) |
| 182 | .filter(Conversation.project_name == project_name) |
| 183 | .order_by(Conversation.updated_at.desc()) |
| 184 | .all() |
| 185 | ) |
| 186 | return [ |
| 187 | { |
| 188 | "id": c.Conversation.id, |
| 189 | "project_name": c.Conversation.project_name, |
| 190 | "title": c.Conversation.title, |
| 191 | "created_at": c.Conversation.created_at.isoformat() if c.Conversation.created_at else None, |
| 192 | "updated_at": c.Conversation.updated_at.isoformat() if c.Conversation.updated_at else None, |
| 193 | "message_count": c.message_count, |
| 194 | } |
| 195 | for c in conversations |
| 196 | ] |
| 197 | finally: |
| 198 | session.close() |
| 199 | |
| 200 | |
| 201 | def get_conversation(project_dir: Path, conversation_id: int) -> Optional[dict]: |
no test coverage detected