UMBRA/backend/app/schemas/project_member.py
Kyle Pope 0a449f166c Polish pass: action all remaining QA suggestions before merge
P-01: Clamp delta poll since param to max 24h in the past (projects +
calendars) to prevent expensive full-table scans from malicious timestamps.

P-02: Validate individual user_id elements in ProjectMemberInvite and
TaskAssignmentCreate with Annotated[int, Field(ge=1, le=2147483647)].

P-04: Only enable delta polling for shared projects (member_count > 0).
Solo projects skip the 5s poll entirely.

P-05: Remove fragile 200ms onBlur timeout in ProjectShareSheet search.
The onMouseDown preventDefault on dropdown items already prevents blur
from firing before click registers.

P-06/S-04: Replace manual dict construction in model_validators with
__table__.columns iteration so new fields are auto-included.

S-01: Replace bare except in ProjectResponse.compute_member_count with
logger.debug to surface errors in development.

S-03: Consolidate cascade_projects_on_disconnect from 2 project ID
queries into 1 using IN clause with both user IDs.

S-05: Send version in toggleTaskMutation, updateTaskStatusMutation,
and toggleSubtaskMutation for full optimistic locking coverage. Handle
409 with refresh toast.

S-07: Replace window.location.href with React Router navigateRef in
task_assigned toast for client-side navigation.

S-08: Already fixed in previous commit (subtask comment selectinload).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 05:28:34 +08:00

44 lines
1.1 KiB
Python

from typing import Annotated, Optional, Literal
from pydantic import BaseModel, ConfigDict, Field
from datetime import datetime
MemberPermission = Literal["read_only", "create_modify"]
MemberStatus = Literal["pending", "accepted", "rejected"]
InviteResponse = Literal["accepted", "rejected"]
class ProjectMemberInvite(BaseModel):
model_config = ConfigDict(extra="forbid")
user_ids: list[Annotated[int, Field(ge=1, le=2147483647)]] = Field(min_length=1, max_length=10)
permission: MemberPermission = "create_modify"
class ProjectMemberUpdate(BaseModel):
model_config = ConfigDict(extra="forbid")
permission: MemberPermission
class ProjectMemberRespond(BaseModel):
model_config = ConfigDict(extra="forbid")
response: InviteResponse
class ProjectMemberResponse(BaseModel):
id: int
project_id: int
user_id: int
invited_by: int
permission: str
status: str
source: str
user_name: str | None = None
inviter_name: str | None = None
created_at: datetime
updated_at: datetime
accepted_at: datetime | None = None
model_config = ConfigDict(from_attributes=True)