anicove / api /models /bug_reports.py
mwask's picture
bug reoprt section
0e74473
Raw
History Blame Contribute Delete
7.01 kB
"""
Bug Reports model layer.
Handles all MongoDB operations for the bug/issue reporting system.
"""
from datetime import datetime, timezone
from bson import ObjectId
from pymongo import DESCENDING
from ..core.db_connector import bug_reports_collection
def _ensure_indexes():
try:
bug_reports_collection.create_index([("created_at", DESCENDING)])
bug_reports_collection.create_index([("status", DESCENDING)])
bug_reports_collection.create_index([("author_id", DESCENDING)])
except Exception:
pass
_ensure_indexes()
def _serialize_report(doc):
"""Convert a MongoDB report document into a JSON-serialisable dict."""
if not doc:
return None
admin_replies = []
for r in doc.get("admin_replies", []):
admin_replies.append({
"_id": str(r.get("_id", ObjectId())),
"body": r.get("body", ""),
"created_at": r["created_at"].isoformat() if r.get("created_at") else None,
"is_broadcast": r.get("is_broadcast", False),
"type": "admin",
})
user_replies = []
for r in doc.get("user_replies", []):
user_replies.append({
"_id": str(r.get("_id", ObjectId())),
"body": r.get("body", ""),
"created_at": r["created_at"].isoformat() if r.get("created_at") else None,
"type": "user",
})
return {
"_id": str(doc["_id"]),
"title": doc.get("title", ""),
"body": doc.get("body", ""),
"category": doc.get("category", "bug"),
"page_url": doc.get("page_url", ""),
"author": doc.get("author", "Anonymous"),
"author_id": str(doc["author_id"]) if doc.get("author_id") is not None else None,
"author_avatar": doc.get("author_avatar"),
"status": doc.get("status", "open"),
"created_at": doc["created_at"].isoformat() if doc.get("created_at") else None,
"updated_at": doc["updated_at"].isoformat() if doc.get("updated_at") else None,
"admin_replies": admin_replies,
"user_replies": user_replies,
"admin_reply_count": len(admin_replies),
"user_reply_count": len(user_replies),
"reply_count": len(admin_replies) + len(user_replies),
}
def create_report(title, body, category, author, author_id, author_avatar=None, page_url=None):
"""Create a new bug report."""
doc = {
"title": title.strip(),
"body": body.strip(),
"category": category or "bug",
"page_url": (page_url or "").strip(),
"author": author,
"author_id": str(author_id) if author_id is not None else None,
"author_avatar": author_avatar,
"status": "open",
"created_at": datetime.now(timezone.utc),
"updated_at": datetime.now(timezone.utc),
"admin_replies": [],
"user_replies": [],
}
result = bug_reports_collection.insert_one(doc)
doc["_id"] = result.inserted_id
return _serialize_report(doc)
def get_reports(page=1, per_page=20, status=None, author_id=None):
"""Get bug reports with pagination and optional filtering."""
query = {}
if status:
query["status"] = status
if author_id:
query["author_id"] = str(author_id)
total = bug_reports_collection.count_documents(query)
skip = (page - 1) * per_page
cursor = bug_reports_collection.find(query).sort("created_at", DESCENDING).skip(skip).limit(per_page)
reports = [_serialize_report(doc) for doc in cursor]
return reports, total
def get_report_by_id(report_id):
"""Get a single report by its ID."""
try:
oid = ObjectId(report_id)
except Exception:
return None
doc = bug_reports_collection.find_one({"_id": oid})
return _serialize_report(doc)
def add_admin_reply(report_id, body, is_broadcast=False):
"""Add an admin reply to a bug report."""
try:
oid = ObjectId(report_id)
except Exception:
return False
reply = {
"_id": ObjectId(),
"body": body.strip(),
"created_at": datetime.now(timezone.utc),
"is_broadcast": is_broadcast,
}
result = bug_reports_collection.update_one(
{"_id": oid},
{
"$push": {"admin_replies": reply},
"$set": {
"status": "in_progress",
"updated_at": datetime.now(timezone.utc),
},
}
)
return result.modified_count > 0
def add_broadcast_reply(report_ids, body):
"""Add the same admin reply to multiple reports (group reply)."""
reply = {
"_id": ObjectId(),
"body": body.strip(),
"created_at": datetime.now(timezone.utc),
"is_broadcast": True,
}
oids = []
for rid in report_ids:
try:
oids.append(ObjectId(rid))
except Exception:
continue
if not oids:
return False
result = bug_reports_collection.update_many(
{"_id": {"$in": oids}},
{
"$push": {"admin_replies": reply},
"$set": {
"status": "in_progress",
"updated_at": datetime.now(timezone.utc),
},
}
)
return result.modified_count > 0
def update_report_status(report_id, new_status):
"""Update the status of a bug report."""
try:
oid = ObjectId(report_id)
except Exception:
return False
valid_statuses = ("open", "in_progress", "resolved", "closed")
if new_status not in valid_statuses:
return False
result = bug_reports_collection.update_one(
{"_id": oid},
{"$set": {"status": new_status, "updated_at": datetime.now(timezone.utc)}}
)
return result.modified_count > 0
def add_user_reply(report_id, author, author_id, body):
"""Add a user reply to their own bug report. Verifies the user owns the report."""
try:
oid = ObjectId(report_id)
except Exception:
return False
reply = {
"_id": ObjectId(),
"author": author,
"author_id": str(author_id) if author_id is not None else None,
"body": body.strip(),
"created_at": datetime.now(timezone.utc),
}
result = bug_reports_collection.update_one(
{"_id": oid},
{
"$push": {"user_replies": reply},
"$set": {
"updated_at": datetime.now(timezone.utc),
},
}
)
return result.modified_count > 0
def get_stats():
"""Get bug report statistics for the admin panel."""
total = bug_reports_collection.count_documents({})
open_count = bug_reports_collection.count_documents({"status": "open"})
in_progress_count = bug_reports_collection.count_documents({"status": "in_progress"})
resolved_count = bug_reports_collection.count_documents({"status": "resolved"})
return {
"total": total,
"open": open_count,
"in_progress": in_progress_count,
"resolved": resolved_count,
}