feat(05-01): create discussion service with CRUD operations
Add discussion.py service with create_discussion, get_discussion, get_active_discussion, list_discussions, and complete_discussion. Uses selectinload for eager loading of rounds, messages, and consensus. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
5afd8b6213
commit
3258c3a596
2 changed files with 141 additions and 1 deletions
|
|
@ -4,6 +4,22 @@ Services encapsulate database operations and business rules,
|
|||
providing a clean interface for handlers to use.
|
||||
"""
|
||||
|
||||
from moai.core.services.discussion import (
|
||||
complete_discussion,
|
||||
create_discussion,
|
||||
get_active_discussion,
|
||||
get_discussion,
|
||||
list_discussions,
|
||||
)
|
||||
from moai.core.services.project import create_project, get_project, list_projects
|
||||
|
||||
__all__ = ["create_project", "get_project", "list_projects"]
|
||||
__all__ = [
|
||||
"complete_discussion",
|
||||
"create_discussion",
|
||||
"create_project",
|
||||
"get_active_discussion",
|
||||
"get_discussion",
|
||||
"get_project",
|
||||
"list_discussions",
|
||||
"list_projects",
|
||||
]
|
||||
|
|
|
|||
124
src/moai/core/services/discussion.py
Normal file
124
src/moai/core/services/discussion.py
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
"""Discussion service for MoAI.
|
||||
|
||||
Provides CRUD operations for discussions, rounds, and messages.
|
||||
"""
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
from moai.core.database import get_session
|
||||
from moai.core.models import (
|
||||
Discussion,
|
||||
DiscussionStatus,
|
||||
DiscussionType,
|
||||
Round,
|
||||
)
|
||||
|
||||
|
||||
async def create_discussion(
|
||||
project_id: str,
|
||||
question: str,
|
||||
discussion_type: DiscussionType,
|
||||
) -> Discussion:
|
||||
"""Create a new discussion within a project.
|
||||
|
||||
Args:
|
||||
project_id: The parent project's UUID.
|
||||
question: The question or topic being discussed.
|
||||
discussion_type: Whether this is OPEN or DISCUSS mode.
|
||||
|
||||
Returns:
|
||||
The created Discussion object.
|
||||
"""
|
||||
async with get_session() as session:
|
||||
discussion = Discussion(
|
||||
project_id=project_id,
|
||||
question=question,
|
||||
type=discussion_type,
|
||||
)
|
||||
session.add(discussion)
|
||||
await session.flush()
|
||||
await session.refresh(discussion)
|
||||
return discussion
|
||||
|
||||
|
||||
async def get_discussion(discussion_id: str) -> Discussion | None:
|
||||
"""Get a discussion by ID with eager loading of rounds and messages.
|
||||
|
||||
Args:
|
||||
discussion_id: The discussion's UUID.
|
||||
|
||||
Returns:
|
||||
The Discussion object if found, None otherwise.
|
||||
"""
|
||||
async with get_session() as session:
|
||||
result = await session.execute(
|
||||
select(Discussion)
|
||||
.where(Discussion.id == discussion_id)
|
||||
.options(
|
||||
selectinload(Discussion.rounds).selectinload(Round.messages),
|
||||
selectinload(Discussion.consensus),
|
||||
)
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
|
||||
async def get_active_discussion(project_id: str) -> Discussion | None:
|
||||
"""Get the active discussion for a project.
|
||||
|
||||
Args:
|
||||
project_id: The project's UUID.
|
||||
|
||||
Returns:
|
||||
The active Discussion object if found, None otherwise.
|
||||
"""
|
||||
async with get_session() as session:
|
||||
result = await session.execute(
|
||||
select(Discussion)
|
||||
.where(Discussion.project_id == project_id)
|
||||
.where(Discussion.status == DiscussionStatus.ACTIVE)
|
||||
.options(
|
||||
selectinload(Discussion.rounds).selectinload(Round.messages),
|
||||
)
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
|
||||
async def list_discussions(project_id: str) -> list[Discussion]:
|
||||
"""List all discussions for a project ordered by creation date (newest first).
|
||||
|
||||
Args:
|
||||
project_id: The project's UUID.
|
||||
|
||||
Returns:
|
||||
List of Discussion objects.
|
||||
"""
|
||||
async with get_session() as session:
|
||||
result = await session.execute(
|
||||
select(Discussion)
|
||||
.where(Discussion.project_id == project_id)
|
||||
.order_by(Discussion.created_at.desc())
|
||||
)
|
||||
return list(result.scalars().all())
|
||||
|
||||
|
||||
async def complete_discussion(discussion_id: str) -> Discussion | None:
|
||||
"""Mark a discussion as completed.
|
||||
|
||||
Args:
|
||||
discussion_id: The discussion's UUID.
|
||||
|
||||
Returns:
|
||||
The updated Discussion object if found, None otherwise.
|
||||
"""
|
||||
async with get_session() as session:
|
||||
result = await session.execute(select(Discussion).where(Discussion.id == discussion_id))
|
||||
discussion = result.scalar_one_or_none()
|
||||
|
||||
if discussion is None:
|
||||
return None
|
||||
|
||||
discussion.status = DiscussionStatus.COMPLETED
|
||||
await session.flush()
|
||||
await session.refresh(discussion)
|
||||
return discussion
|
||||
Loading…
Add table
Reference in a new issue