feat: add organization-related models and update schemas for organization management
This commit is contained in:
@@ -17,7 +17,8 @@ from sse_starlette.sse import EventSourceResponse
|
||||
|
||||
from app.api.deps import (
|
||||
ActorContext,
|
||||
get_board_or_404,
|
||||
get_board_for_actor_read,
|
||||
get_board_for_user_write,
|
||||
get_task_or_404,
|
||||
require_admin_auth,
|
||||
require_admin_or_agent,
|
||||
@@ -42,6 +43,7 @@ from app.schemas.pagination import DefaultLimitOffsetPage
|
||||
from app.schemas.tasks import TaskCommentCreate, TaskCommentRead, TaskCreate, TaskRead, TaskUpdate
|
||||
from app.services.activity_log import record_activity
|
||||
from app.services.mentions import extract_mentions, matches_agent_mention
|
||||
from app.services.organizations import require_board_access
|
||||
from app.services.task_dependencies import (
|
||||
blocked_by_dependency_ids,
|
||||
dependency_ids_by_task_id,
|
||||
@@ -442,7 +444,7 @@ async def _notify_lead_on_task_unassigned(
|
||||
@router.get("/stream")
|
||||
async def stream_tasks(
|
||||
request: Request,
|
||||
board: Board = Depends(get_board_or_404),
|
||||
board: Board = Depends(get_board_for_actor_read),
|
||||
actor: ActorContext = Depends(require_admin_or_agent),
|
||||
since: str | None = Query(default=None),
|
||||
) -> EventSourceResponse:
|
||||
@@ -525,13 +527,10 @@ async def list_tasks(
|
||||
status_filter: str | None = Query(default=None, alias="status"),
|
||||
assigned_agent_id: UUID | None = None,
|
||||
unassigned: bool | None = None,
|
||||
board: Board = Depends(get_board_or_404),
|
||||
board: Board = Depends(get_board_for_actor_read),
|
||||
session: AsyncSession = Depends(get_session),
|
||||
actor: ActorContext = Depends(require_admin_or_agent),
|
||||
) -> DefaultLimitOffsetPage[TaskRead]:
|
||||
if actor.actor_type == "agent" and actor.agent:
|
||||
if actor.agent.board_id and actor.agent.board_id != board.id:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
||||
statement = select(Task).where(Task.board_id == board.id)
|
||||
if status_filter:
|
||||
statuses = [s.strip() for s in status_filter.split(",") if s.strip()]
|
||||
@@ -586,7 +585,7 @@ async def list_tasks(
|
||||
@router.post("", response_model=TaskRead, responses={409: {"model": BlockedTaskError}})
|
||||
async def create_task(
|
||||
payload: TaskCreate,
|
||||
board: Board = Depends(get_board_or_404),
|
||||
board: Board = Depends(get_board_for_user_write),
|
||||
session: AsyncSession = Depends(get_session),
|
||||
auth: AuthContext = Depends(require_admin_auth),
|
||||
) -> TaskRead:
|
||||
@@ -669,6 +668,11 @@ async def update_task(
|
||||
detail="Task board_id is required.",
|
||||
)
|
||||
board_id = task.board_id
|
||||
if actor.actor_type == "user" and actor.user is not None:
|
||||
board = await session.get(Board, board_id)
|
||||
if board is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||
await require_board_access(session, user=actor.user, board=board, write=True)
|
||||
|
||||
previous_status = task.status
|
||||
previous_assigned = task.assigned_agent_id
|
||||
@@ -978,6 +982,14 @@ async def delete_task(
|
||||
task: Task = Depends(get_task_or_404),
|
||||
auth: AuthContext = Depends(require_admin_auth),
|
||||
) -> OkResponse:
|
||||
if task.board_id is None:
|
||||
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY)
|
||||
board = await session.get(Board, task.board_id)
|
||||
if board is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||
if auth.user is None:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
|
||||
await require_board_access(session, user=auth.user, board=board, write=True)
|
||||
await session.execute(delete(ActivityEvent).where(col(ActivityEvent.task_id) == task.id))
|
||||
await session.execute(delete(TaskFingerprint).where(col(TaskFingerprint.task_id) == task.id))
|
||||
await session.execute(delete(Approval).where(col(Approval.task_id) == task.id))
|
||||
@@ -998,11 +1010,7 @@ async def delete_task(
|
||||
async def list_task_comments(
|
||||
task: Task = Depends(get_task_or_404),
|
||||
session: AsyncSession = Depends(get_session),
|
||||
actor: ActorContext = Depends(require_admin_or_agent),
|
||||
) -> DefaultLimitOffsetPage[TaskCommentRead]:
|
||||
if actor.actor_type == "agent" and actor.agent:
|
||||
if actor.agent.board_id and task.board_id and actor.agent.board_id != task.board_id:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
||||
statement = (
|
||||
select(ActivityEvent)
|
||||
.where(col(ActivityEvent.task_id) == task.id)
|
||||
@@ -1019,6 +1027,13 @@ async def create_task_comment(
|
||||
session: AsyncSession = Depends(get_session),
|
||||
actor: ActorContext = Depends(require_admin_or_agent),
|
||||
) -> ActivityEvent:
|
||||
if task.board_id is None:
|
||||
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY)
|
||||
if actor.actor_type == "user" and actor.user is not None:
|
||||
board = await session.get(Board, task.board_id)
|
||||
if board is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||
await require_board_access(session, user=actor.user, board=board, write=True)
|
||||
if actor.actor_type == "agent" and actor.agent:
|
||||
if actor.agent.is_board_lead and task.status != "review":
|
||||
if not await _lead_was_mentioned(session, task, actor.agent) and not _lead_created_task(
|
||||
@@ -1030,8 +1045,6 @@ async def create_task_comment(
|
||||
"Board leads can only comment during review, when mentioned, or on tasks they created."
|
||||
),
|
||||
)
|
||||
if actor.agent.board_id and task.board_id and actor.agent.board_id != task.board_id:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
||||
event = ActivityEvent(
|
||||
event_type="task.comment",
|
||||
message=payload.message,
|
||||
|
||||
Reference in New Issue
Block a user