feat: implement comments and activity panel in workspace
- Added CommentsPanel component for managing comments on documents. - Integrated comment creation, deletion, and resolution functionalities. - Implemented activity tracking for document interactions. - Enhanced SketchPage with editing and saving capabilities. - Introduced template management API endpoints for creating, sharing, and deleting templates. - Added runtime validation report for frontend and backend. - Created GettingStartedPanel for user onboarding with template options. - Implemented Playwright E2E tests for accessibility and export functionality.
This commit is contained in:
parent
6e354ba041
commit
809235e962
41 changed files with 2840 additions and 285 deletions
81
backend/app/services/history_service.py
Normal file
81
backend/app/services/history_service.py
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
import uuid
|
||||
from datetime import UTC, datetime
|
||||
from typing import Any
|
||||
|
||||
from motor.motor_asyncio import AsyncIOMotorDatabase
|
||||
from pymongo import ReturnDocument
|
||||
|
||||
from app.services.document_service import access_query, can_edit
|
||||
|
||||
COLLECTION = "document_versions"
|
||||
|
||||
|
||||
async def ensure_history_indexes(db: AsyncIOMotorDatabase) -> None:
|
||||
await db[COLLECTION].create_index([("document_id", 1), ("created_at", -1)])
|
||||
|
||||
|
||||
def serialize_version(version: dict[str, Any]) -> dict[str, Any]:
|
||||
return {
|
||||
"id": version["_id"],
|
||||
"document_id": version["document_id"],
|
||||
"title": version["title"],
|
||||
"content": version.get("content", {}),
|
||||
"created_by": version["created_by"],
|
||||
"created_at": version["created_at"],
|
||||
"reason": version.get("reason", "Updated"),
|
||||
}
|
||||
|
||||
|
||||
async def create_version(
|
||||
db: AsyncIOMotorDatabase,
|
||||
document: dict[str, Any],
|
||||
actor_id: int,
|
||||
actor_name: str,
|
||||
reason: str,
|
||||
) -> None:
|
||||
if not can_edit(document, actor_id):
|
||||
return
|
||||
await db[COLLECTION].insert_one({
|
||||
"_id": str(uuid.uuid4()),
|
||||
"document_id": document["_id"],
|
||||
"title": document["title"],
|
||||
"content": document.get("content", {}),
|
||||
"created_by": actor_name,
|
||||
"created_at": datetime.now(UTC),
|
||||
"reason": reason,
|
||||
})
|
||||
|
||||
|
||||
async def list_versions(
|
||||
db: AsyncIOMotorDatabase,
|
||||
document_id: str,
|
||||
user_id: int,
|
||||
) -> list[dict[str, Any]] | None:
|
||||
document = await db["documents"].find_one({"_id": document_id, **access_query(user_id)})
|
||||
if document is None:
|
||||
return None
|
||||
cursor = db[COLLECTION].find({"document_id": document_id}).sort("created_at", -1).limit(50)
|
||||
return [serialize_version(version) async for version in cursor]
|
||||
|
||||
|
||||
async def restore_version(
|
||||
db: AsyncIOMotorDatabase,
|
||||
document_id: str,
|
||||
version_id: str,
|
||||
user_id: int,
|
||||
actor_name: str,
|
||||
) -> dict[str, Any] | None:
|
||||
document = await db["documents"].find_one({"_id": document_id, **access_query(user_id)})
|
||||
if document is None or not can_edit(document, user_id):
|
||||
return None
|
||||
version = await db[COLLECTION].find_one({"_id": version_id, "document_id": document_id})
|
||||
if version is None:
|
||||
return None
|
||||
await create_version(db, document, user_id, actor_name, "Before restore")
|
||||
now = datetime.now(UTC)
|
||||
updated = await db["documents"].find_one_and_update(
|
||||
{"_id": document_id},
|
||||
{"$set": {"title": version["title"], "content": version.get("content", {}), "updated_at": now}},
|
||||
return_document=ReturnDocument.AFTER,
|
||||
)
|
||||
return updated
|
||||
Loading…
Add table
Add a link
Reference in a new issue