forked from retoor/devplacepy
- Route Devii-driven AI gateway cost to the action/tool that triggered it instead of a blanket "internal" bucket, so per-feature AI spend is attributable. - Fix the quiz attempt review to show one previously-answered question at a time instead of all of them at once, and stop a quiz endpoint linked from the quiz flow from responding with raw JSON. - Add DB API async query result route and AI Usage Analyzer annotated source/media routes, with traversal-safe uid/path handling and matching tests. - Add Code Farm action audit logging (plant/harvest/buy-plot/upgrade/ fertilize) and related admin workspace/services/trash/gateway route and doc touch-ups. - Drop redundant docstrings from access_tokens.py per the no-comments convention. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VL9Xn57W5UR3HZbbuuzxdK
90 lines
3.1 KiB
Python
90 lines
3.1 KiB
Python
# retoor <retoor@molodetz.nl>
|
|
|
|
import logging
|
|
from typing import Annotated
|
|
|
|
from fastapi import Depends, APIRouter, Request
|
|
|
|
from devplacepy.attachments import (
|
|
get_orphan_attachments_batch,
|
|
link_attachments,
|
|
mirror_attachment_to_gitea,
|
|
split_attachment_uids,
|
|
)
|
|
from devplacepy.database import get_table
|
|
from devplacepy.models import IssueCommentForm
|
|
from devplacepy.responses import action_result, json_error
|
|
from devplacepy.services.audit import record as audit
|
|
from devplacepy.services.gitea import runtime, store
|
|
from devplacepy.services.gitea.client import GiteaError
|
|
from devplacepy.services.gitea.config import gitea_config
|
|
from devplacepy.services.background import background
|
|
from devplacepy.utils import create_notification, require_user
|
|
from devplacepy.dependencies import json_or_form
|
|
|
|
logger = logging.getLogger(__name__)
|
|
router = APIRouter()
|
|
|
|
|
|
def _notify_admins(actor: dict, number: int) -> None:
|
|
for admin in get_table("users").find(role="Admin"):
|
|
if admin["uid"] == actor["uid"]:
|
|
continue
|
|
create_notification(
|
|
admin["uid"],
|
|
"issue",
|
|
f"{actor['username']} commented on issue #{number}",
|
|
str(number),
|
|
f"/issues/{number}",
|
|
)
|
|
|
|
@router.post("/{number}/comment")
|
|
async def comment_issue(
|
|
request: Request, number: int, data: Annotated[IssueCommentForm, Depends(json_or_form(IssueCommentForm))]
|
|
):
|
|
user = require_user(request)
|
|
if not gitea_config().is_configured:
|
|
return json_error(503, "The issue tracker is not configured")
|
|
client = runtime.get_client()
|
|
body = (
|
|
f"{data.body.strip()}\n\n---\n"
|
|
f"*Posted by **{user['username']}** via DevPlace.*"
|
|
)
|
|
try:
|
|
comment = await client.create_comment(number, body)
|
|
except GiteaError as exc:
|
|
logger.warning("Could not post comment on #%s: %s", number, exc)
|
|
audit.record(
|
|
request,
|
|
"issue.comment",
|
|
user=user,
|
|
result="failure",
|
|
target_type="issue",
|
|
target_uid=str(number),
|
|
summary=f"Failed to comment on issue #{number}: {exc}",
|
|
)
|
|
return json_error(exc.status or 502, "Could not post the comment")
|
|
|
|
comment_id = int(comment.get("id", 0))
|
|
store.record_comment_author(comment_id, number, user["uid"])
|
|
owned = get_orphan_attachments_batch(
|
|
split_attachment_uids(data.attachment_uids), user
|
|
)
|
|
if owned:
|
|
link_attachments(owned, "issue_comment", str(comment_id))
|
|
for uid in owned:
|
|
await mirror_attachment_to_gitea(uid)
|
|
background.submit(_notify_admins, user, number)
|
|
audit.record(
|
|
request,
|
|
"issue.comment",
|
|
user=user,
|
|
target_type="issue",
|
|
target_uid=str(number),
|
|
summary=f"{user['username']} commented on issue #{number}",
|
|
metadata={"number": number, "comment_id": comment_id},
|
|
links=[audit.target("issue", str(number))],
|
|
)
|
|
logger.info("Comment posted on issue #%s by %s", number, user["username"])
|
|
return action_result(request, f"/issues/{number}", data={"comment_id": comment_id})
|