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
77
backend/app/services/template_service.py
Normal file
77
backend/app/services/template_service.py
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
import uuid
|
||||
from datetime import UTC, datetime
|
||||
from typing import Any
|
||||
|
||||
from motor.motor_asyncio import AsyncIOMotorDatabase
|
||||
from pymongo import ReturnDocument
|
||||
|
||||
COLLECTION = "templates"
|
||||
|
||||
|
||||
async def ensure_template_indexes(db: AsyncIOMotorDatabase) -> None:
|
||||
await db[COLLECTION].create_index([("owner_id", 1), ("updated_at", -1)])
|
||||
await db[COLLECTION].create_index([("owner_id", 1), ("category", 1)])
|
||||
|
||||
|
||||
def _serialize(template: dict[str, Any]) -> dict[str, Any]:
|
||||
return {
|
||||
"id": template["_id"],
|
||||
"owner_id": template["owner_id"],
|
||||
"name": template["name"],
|
||||
"description": template.get("description", ""),
|
||||
"category": template.get("category", "General"),
|
||||
"emoji": template.get("emoji", "📄"),
|
||||
"content": template.get("content", {}),
|
||||
"created_at": template["created_at"],
|
||||
"updated_at": template["updated_at"],
|
||||
"members": template.get("members", []),
|
||||
}
|
||||
|
||||
|
||||
async def list_templates(db: AsyncIOMotorDatabase, owner_id: int) -> list[dict[str, Any]]:
|
||||
cursor = db[COLLECTION].find(
|
||||
{"$or": [{"owner_id": owner_id}, {"members.user_id": owner_id}]}
|
||||
).sort([("category", 1), ("updated_at", -1)])
|
||||
return [_serialize(template) async for template in cursor]
|
||||
|
||||
|
||||
async def create_template(
|
||||
db: AsyncIOMotorDatabase,
|
||||
owner_id: int,
|
||||
data: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
now = datetime.now(UTC)
|
||||
template = {
|
||||
"_id": str(uuid.uuid4()),
|
||||
"owner_id": owner_id,
|
||||
**data,
|
||||
"created_at": now,
|
||||
"updated_at": now,
|
||||
"members": [],
|
||||
}
|
||||
await db[COLLECTION].insert_one(template)
|
||||
return _serialize(template)
|
||||
|
||||
|
||||
async def delete_template(db: AsyncIOMotorDatabase, template_id: str, owner_id: int) -> bool:
|
||||
result = await db[COLLECTION].delete_one({"_id": template_id, "owner_id": owner_id})
|
||||
return result.deleted_count == 1
|
||||
|
||||
|
||||
async def share_template(
|
||||
db: AsyncIOMotorDatabase,
|
||||
template_id: str,
|
||||
owner_id: int,
|
||||
member: dict[str, Any],
|
||||
) -> dict[str, Any] | None:
|
||||
template = await db[COLLECTION].find_one({"_id": template_id, "owner_id": owner_id})
|
||||
if template is None:
|
||||
return None
|
||||
members = [item for item in template.get("members", []) if item.get("user_id") != member["user_id"]]
|
||||
members.append(member)
|
||||
result = await db[COLLECTION].find_one_and_update(
|
||||
{"_id": template_id, "owner_id": owner_id},
|
||||
{"$set": {"members": members, "updated_at": datetime.now(UTC)}},
|
||||
return_document=ReturnDocument.AFTER,
|
||||
)
|
||||
return _serialize(result) if result else None
|
||||
Loading…
Add table
Add a link
Reference in a new issue