"""Service for posting comments on Forgejo issues and updating local cache.""" from __future__ import annotations from datetime import datetime from typing import TYPE_CHECKING from uuid import UUID from sqlmodel import select from app.core.logging import get_logger from app.core.time import utcnow from app.db import crud from app.models.forgejo_connections import ForgejoConnection from app.models.forgejo_issues import ForgejoIssue from app.models.forgejo_repositories import ForgejoRepository from app.services.forgejo_client import get_forgejo_client if TYPE_CHECKING: from sqlalchemy.ext.asyncio.session import AsyncSession logger = get_logger(__name__) class PostCommentError(Exception): """Base exception for post comment errors.""" class PostCommentNotFoundError(PostCommentError): """Raised when issue, repository, or connection is not found.""" class PostCommentRemoteError(PostCommentError): """Raised when the Forgejo API call fails.""" async def post_comment_on_issue( session: AsyncSession, issue: ForgejoIssue, body: str, actor_user_id: UUID | None = None, actor_agent_id: UUID | None = None, ) -> dict[str, object]: """Post a comment on a Forgejo issue and append it to the local cache. Raises: PostCommentNotFoundError: If repository or connection not found. PostCommentRemoteError: If the Forgejo API call fails. """ repository = await crud.get_by_id(session, ForgejoRepository, issue.repository_id) if repository is None: raise PostCommentNotFoundError("Repository not found") connection = await crud.get_by_id(session, ForgejoConnection, repository.connection_id) if connection is None: raise PostCommentNotFoundError("Connection not found") try: async with get_forgejo_client(connection) as client: created = await client.create_comment( owner=repository.owner, repo=repository.repo, issue_number=issue.forgejo_issue_number, body=body, ) except Exception as exc: raise PostCommentRemoteError(f"Failed to post comment on Forgejo: {exc}") from exc existing = list(issue.forgejo_comments_payload or []) existing.append(created) issue.forgejo_comments_payload = existing issue.last_synced_at = utcnow() session.add(issue) await session.flush() raw_created_at = created.get("created_at") created_at_str = ( raw_created_at.isoformat() if isinstance(raw_created_at, datetime) else str(raw_created_at or "") ) logger.info( "forgejo.issue.comment.posted", extra={ "issue_id": str(issue.id), "forgejo_issue_number": issue.forgejo_issue_number, "repository_id": str(repository.id), "comment_id": str(created.get("id", "")), "actor_user_id": str(actor_user_id) if actor_user_id else None, "actor_agent_id": str(actor_agent_id) if actor_agent_id else None, }, ) return { "success": True, "issue_id": str(issue.id), "comment_id": created.get("id"), "body": body, "created_at": created_at_str, } async def post_comment_by_issue_id( session: AsyncSession, issue_id: UUID, body: str, actor_user_id: UUID | None = None, actor_agent_id: UUID | None = None, ) -> dict[str, object]: """Post a comment by issue ID.""" issue = (await session.exec(select(ForgejoIssue).where(ForgejoIssue.id == issue_id))).first() if issue is None: raise PostCommentNotFoundError("Issue not found") return await post_comment_on_issue( session=session, issue=issue, body=body, actor_user_id=actor_user_id, actor_agent_id=actor_agent_id, )