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.
|
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
|
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