feat: implement collaboration module

- Backend (FastAPI):
  - Task comments with nested replies and soft delete
  - @mention parsing with 10-mention limit per comment
  - Notification system with read/unread tracking
  - Blocker management with project owner notification
  - WebSocket endpoint with JWT auth and keepalive
  - User search API for @mention autocomplete
  - Alembic migration for 4 new tables

- Frontend (React + Vite):
  - Comments component with @mention autocomplete
  - NotificationBell with real-time WebSocket updates
  - BlockerDialog for task blocking workflow
  - NotificationContext for state management

- OpenSpec:
  - 4 requirements with scenarios defined
  - add-collaboration change archived

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
beabigegg
2025-12-29 20:45:07 +08:00
parent 61fe01cb6b
commit 3470428411
38 changed files with 3088 additions and 4 deletions

View File

@@ -11,6 +11,15 @@ from app.schemas.task import (
TaskCreate, TaskUpdate, TaskResponse, TaskWithDetails, TaskListResponse,
TaskStatusUpdate as TaskStatusChangeUpdate, TaskAssignUpdate, Priority
)
from app.schemas.comment import (
CommentCreate, CommentUpdate, CommentResponse, CommentListResponse
)
from app.schemas.notification import (
NotificationResponse, NotificationListResponse, UnreadCountResponse
)
from app.schemas.blocker import (
BlockerCreate, BlockerResolve, BlockerResponse, BlockerListResponse
)
__all__ = [
"LoginRequest",
@@ -44,4 +53,15 @@ __all__ = [
"TaskStatusChangeUpdate",
"TaskAssignUpdate",
"Priority",
"CommentCreate",
"CommentUpdate",
"CommentResponse",
"CommentListResponse",
"NotificationResponse",
"NotificationListResponse",
"UnreadCountResponse",
"BlockerCreate",
"BlockerResolve",
"BlockerResponse",
"BlockerListResponse",
]

View File

@@ -0,0 +1,39 @@
from datetime import datetime
from typing import Optional, List
from pydantic import BaseModel, Field
class BlockerCreate(BaseModel):
reason: str = Field(..., min_length=1, max_length=2000)
class BlockerResolve(BaseModel):
resolution_note: str = Field(..., min_length=1, max_length=2000)
class BlockerUserInfo(BaseModel):
id: str
name: str
email: str
class Config:
from_attributes = True
class BlockerResponse(BaseModel):
id: str
task_id: str
reason: str
resolution_note: Optional[str]
created_at: datetime
resolved_at: Optional[datetime]
reporter: BlockerUserInfo
resolver: Optional[BlockerUserInfo]
class Config:
from_attributes = True
class BlockerListResponse(BaseModel):
blockers: List[BlockerResponse]
total: int

View File

@@ -0,0 +1,52 @@
from datetime import datetime
from typing import Optional, List
from pydantic import BaseModel, Field
class CommentCreate(BaseModel):
content: str = Field(..., min_length=1, max_length=10000)
parent_comment_id: Optional[str] = None
class CommentUpdate(BaseModel):
content: str = Field(..., min_length=1, max_length=10000)
class CommentAuthor(BaseModel):
id: str
name: str
email: str
class Config:
from_attributes = True
class MentionedUser(BaseModel):
id: str
name: str
email: str
class Config:
from_attributes = True
class CommentResponse(BaseModel):
id: str
task_id: str
parent_comment_id: Optional[str]
content: str
is_edited: bool
is_deleted: bool
created_at: datetime
updated_at: datetime
author: CommentAuthor
mentions: List[MentionedUser] = []
reply_count: int = 0
class Config:
from_attributes = True
class CommentListResponse(BaseModel):
comments: List[CommentResponse]
total: int

View File

@@ -0,0 +1,28 @@
from datetime import datetime
from typing import Optional, List
from pydantic import BaseModel
class NotificationResponse(BaseModel):
id: str
type: str
reference_type: str
reference_id: str
title: str
message: Optional[str]
is_read: bool
created_at: datetime
read_at: Optional[datetime]
class Config:
from_attributes = True
class NotificationListResponse(BaseModel):
notifications: List[NotificationResponse]
total: int
unread_count: int
class UnreadCountResponse(BaseModel):
unread_count: int