refactor: all task mutations require auth — actor from JWT/token, no ?actor= or ?slug= params
Some checks failed
Deploy Tracker / deploy (push) Failing after 1s
Some checks failed
Deploy Tracker / deploy (push) Failing after 1s
This commit is contained in:
parent
2f0c4d1636
commit
904c105174
@ -11,6 +11,7 @@ from sqlalchemy.orm import selectinload, joinedload
|
|||||||
|
|
||||||
from tracker.database import get_db
|
from tracker.database import get_db
|
||||||
from tracker.models import Task, Step, Project, Member, Message, Chat
|
from tracker.models import Task, Step, Project, Member, Message, Chat
|
||||||
|
from tracker.api.auth import get_current_member
|
||||||
|
|
||||||
router = APIRouter(tags=["tasks"])
|
router = APIRouter(tags=["tasks"])
|
||||||
|
|
||||||
@ -231,6 +232,7 @@ async def get_task(task_id: str, db: AsyncSession = Depends(get_db)):
|
|||||||
async def create_task(
|
async def create_task(
|
||||||
project_slug: str = Query(...),
|
project_slug: str = Query(...),
|
||||||
req: TaskCreate = ...,
|
req: TaskCreate = ...,
|
||||||
|
current_member: Member = Depends(get_current_member),
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
):
|
):
|
||||||
# Find project
|
# Find project
|
||||||
@ -298,14 +300,14 @@ async def create_task(
|
|||||||
async def update_task(
|
async def update_task(
|
||||||
task_id: str,
|
task_id: str,
|
||||||
req: TaskUpdate,
|
req: TaskUpdate,
|
||||||
actor: Optional[str] = Query(None, description="Who made the change"),
|
current_member: Member = Depends(get_current_member),
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
):
|
):
|
||||||
task = await _get_task(task_id, db)
|
task = await _get_task(task_id, db)
|
||||||
slug = task.project.slug if task.project else ""
|
slug = task.project.slug if task.project else ""
|
||||||
prefix = slug[:2].upper() if slug else "XX"
|
prefix = slug[:2].upper() if slug else "XX"
|
||||||
key = f"{prefix}-{task.number}"
|
key = f"{prefix}-{task.number}"
|
||||||
who = f"@{actor}" if actor else "Кто-то"
|
who = f"@{current_member.slug}"
|
||||||
|
|
||||||
old_status = task.status
|
old_status = task.status
|
||||||
old_assignee = task.assignee_slug
|
old_assignee = task.assignee_slug
|
||||||
@ -338,7 +340,7 @@ async def update_task(
|
|||||||
chat_text=f"{key}: {old_status} → {req.status}",
|
chat_text=f"{key}: {old_status} → {req.status}",
|
||||||
task_text=f"{who} изменил статус: {old_status} → {req.status} ({now_str})",
|
task_text=f"{who} изменил статус: {old_status} → {req.status} ({now_str})",
|
||||||
project_slug=slug,
|
project_slug=slug,
|
||||||
actor_slug=actor or "system",
|
actor_slug=current_member.slug,
|
||||||
)
|
)
|
||||||
if req.assignee_slug is not None and req.assignee_slug != old_assignee:
|
if req.assignee_slug is not None and req.assignee_slug != old_assignee:
|
||||||
if req.assignee_slug:
|
if req.assignee_slug:
|
||||||
@ -347,7 +349,7 @@ async def update_task(
|
|||||||
chat_text=f"{key}: назначена на @{req.assignee_slug}",
|
chat_text=f"{key}: назначена на @{req.assignee_slug}",
|
||||||
task_text=f"{who} назначил задачу на @{req.assignee_slug} ({now_str})",
|
task_text=f"{who} назначил задачу на @{req.assignee_slug} ({now_str})",
|
||||||
project_slug=slug,
|
project_slug=slug,
|
||||||
actor_slug=actor or "system",
|
actor_slug=current_member.slug,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
await _system_message(
|
await _system_message(
|
||||||
@ -355,7 +357,7 @@ async def update_task(
|
|||||||
chat_text=f"{key}: исполнитель снят",
|
chat_text=f"{key}: исполнитель снят",
|
||||||
task_text=f"{who} снял исполнителя @{old_assignee} ({now_str})",
|
task_text=f"{who} снял исполнителя @{old_assignee} ({now_str})",
|
||||||
project_slug=slug,
|
project_slug=slug,
|
||||||
actor_slug=actor or "system",
|
actor_slug=current_member.slug,
|
||||||
)
|
)
|
||||||
|
|
||||||
await db.commit()
|
await db.commit()
|
||||||
@ -375,7 +377,7 @@ async def update_task(
|
|||||||
|
|
||||||
|
|
||||||
@router.delete("/tasks/{task_id}")
|
@router.delete("/tasks/{task_id}")
|
||||||
async def delete_task(task_id: str, db: AsyncSession = Depends(get_db)):
|
async def delete_task(task_id: str, current_member: Member = Depends(get_current_member), db: AsyncSession = Depends(get_db)):
|
||||||
task = await _get_task(task_id, db)
|
task = await _get_task(task_id, db)
|
||||||
project_id = str(task.project_id)
|
project_id = str(task.project_id)
|
||||||
task_data = {"id": str(task.id), "project_id": project_id}
|
task_data = {"id": str(task.id), "project_id": project_id}
|
||||||
@ -387,8 +389,9 @@ async def delete_task(task_id: str, db: AsyncSession = Depends(get_db)):
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/tasks/{task_id}/take", response_model=TaskOut)
|
@router.post("/tasks/{task_id}/take", response_model=TaskOut)
|
||||||
async def take_task(task_id: str, slug: str = Query(...), db: AsyncSession = Depends(get_db)):
|
async def take_task(task_id: str, current_member: Member = Depends(get_current_member), db: AsyncSession = Depends(get_db)):
|
||||||
"""Atomically take a task — only if unassigned and in backlog/todo."""
|
"""Atomically take a task — only if unassigned and in backlog/todo."""
|
||||||
|
slug = current_member.slug
|
||||||
task = await _get_task(task_id, db)
|
task = await _get_task(task_id, db)
|
||||||
if task.assignee_slug:
|
if task.assignee_slug:
|
||||||
raise HTTPException(409, f"Task already assigned to {task.assignee_slug}")
|
raise HTTPException(409, f"Task already assigned to {task.assignee_slug}")
|
||||||
@ -431,7 +434,7 @@ async def take_task(task_id: str, slug: str = Query(...), db: AsyncSession = Dep
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/tasks/{task_id}/reject")
|
@router.post("/tasks/{task_id}/reject")
|
||||||
async def reject_task(task_id: str, req: RejectRequest, db: AsyncSession = Depends(get_db)):
|
async def reject_task(task_id: str, req: RejectRequest, current_member: Member = Depends(get_current_member), db: AsyncSession = Depends(get_db)):
|
||||||
"""Reject a task with reason — unassign and return to todo."""
|
"""Reject a task with reason — unassign and return to todo."""
|
||||||
task = await _get_task(task_id, db)
|
task = await _get_task(task_id, db)
|
||||||
old_assignee = task.assignee_slug
|
old_assignee = task.assignee_slug
|
||||||
@ -456,7 +459,7 @@ async def reject_task(task_id: str, req: RejectRequest, db: AsyncSession = Depen
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/tasks/{task_id}/assign", response_model=TaskOut)
|
@router.post("/tasks/{task_id}/assign", response_model=TaskOut)
|
||||||
async def assign_task(task_id: str, req: AssignRequest, db: AsyncSession = Depends(get_db)):
|
async def assign_task(task_id: str, req: AssignRequest, current_member: Member = Depends(get_current_member), db: AsyncSession = Depends(get_db)):
|
||||||
"""Assign task to a member."""
|
"""Assign task to a member."""
|
||||||
# Verify assignee exists
|
# Verify assignee exists
|
||||||
result = await db.execute(select(Member).where(Member.slug == req.assignee_slug))
|
result = await db.execute(select(Member).where(Member.slug == req.assignee_slug))
|
||||||
@ -495,7 +498,8 @@ async def assign_task(task_id: str, req: AssignRequest, db: AsyncSession = Depen
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/tasks/{task_id}/watch")
|
@router.post("/tasks/{task_id}/watch")
|
||||||
async def watch_task(task_id: str, slug: str = Query(...), db: AsyncSession = Depends(get_db)):
|
async def watch_task(task_id: str, current_member: Member = Depends(get_current_member), db: AsyncSession = Depends(get_db)):
|
||||||
|
slug = current_member.slug
|
||||||
task = await _get_task(task_id, db)
|
task = await _get_task(task_id, db)
|
||||||
if slug not in (task.watchers or []):
|
if slug not in (task.watchers or []):
|
||||||
task.watchers = (task.watchers or []) + [slug]
|
task.watchers = (task.watchers or []) + [slug]
|
||||||
@ -504,7 +508,8 @@ async def watch_task(task_id: str, slug: str = Query(...), db: AsyncSession = De
|
|||||||
|
|
||||||
|
|
||||||
@router.delete("/tasks/{task_id}/watch")
|
@router.delete("/tasks/{task_id}/watch")
|
||||||
async def unwatch_task(task_id: str, slug: str = Query(...), db: AsyncSession = Depends(get_db)):
|
async def unwatch_task(task_id: str, current_member: Member = Depends(get_current_member), db: AsyncSession = Depends(get_db)):
|
||||||
|
slug = current_member.slug
|
||||||
task = await _get_task(task_id, db)
|
task = await _get_task(task_id, db)
|
||||||
task.watchers = [w for w in (task.watchers or []) if w != slug]
|
task.watchers = [w for w in (task.watchers or []) if w != slug]
|
||||||
await db.commit()
|
await db.commit()
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user