Fix activities endpoints to match established project-access convention

get_project_activities, get_task_activities, and get_recent_activities were
gating on "project member OR admin", incorrectly blocking coordinators,
directors, and developers from projects they weren't explicitly added to as
members. Every other project-scoped router (shots.py, assets.py) only
restricts the artist role this way — everyone else has access regardless of
membership. Brought activities.py in line with that convention.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-18 04:18:07 +08:00
parent 4f9deeb57a
commit 9527f06b3f
+51 -47
View File
@@ -5,7 +5,7 @@ from typing import List, Optional
from datetime import datetime, timedelta
from database import get_db
from models.user import User
from models.user import User, UserRole
from models.activity import Activity, ActivityType
from models.project import ProjectMember
from schemas.activity import ActivityResponse
@@ -26,15 +26,17 @@ def get_project_activities(
current_user: User = Depends(get_current_user)
):
"""Get activity feed for a specific project (excludes activities for deleted records)."""
# Verify user has access to the project
member = db.query(ProjectMember).filter(
ProjectMember.project_id == project_id,
ProjectMember.user_id == current_user.id
).first()
if not member and not current_user.is_admin:
from fastapi import HTTPException
raise HTTPException(status_code=403, detail="Access denied to this project")
# Only artists are restricted to their explicit project memberships; coordinators,
# directors, developers, and admins have access to all projects (matches shots.py/assets.py).
if current_user.role == UserRole.ARTIST:
member = db.query(ProjectMember).filter(
ProjectMember.project_id == project_id,
ProjectMember.user_id == current_user.id
).first()
if not member:
from fastapi import HTTPException
raise HTTPException(status_code=403, detail="Access denied to this project")
# Use ActivityService to get activities excluding deleted records
activities = ActivityService.get_activities_excluding_deleted(
@@ -66,15 +68,17 @@ def get_task_activities(
from fastapi import HTTPException
raise HTTPException(status_code=404, detail="Task not found")
# Check if user is a member of the project
member = db.query(ProjectMember).filter(
ProjectMember.project_id == task.project_id,
ProjectMember.user_id == current_user.id
).first()
if not member and not current_user.is_admin:
from fastapi import HTTPException
raise HTTPException(status_code=403, detail="Access denied to this task")
# Only artists are restricted to their explicit project memberships; coordinators,
# directors, developers, and admins have access to all tasks (matches shots.py/assets.py).
if current_user.role == UserRole.ARTIST:
member = db.query(ProjectMember).filter(
ProjectMember.project_id == task.project_id,
ProjectMember.user_id == current_user.id
).first()
if not member:
from fastapi import HTTPException
raise HTTPException(status_code=403, detail="Access denied to this task")
# Use ActivityService to get activities excluding deleted records
activities = ActivityService.get_activities_excluding_deleted(
@@ -122,34 +126,10 @@ def get_recent_activities(
current_user: User = Depends(get_current_user)
):
"""Get recent activities from all projects the user has access to (excludes activities for deleted records)."""
# Get all projects the user is a member of
project_ids = db.query(ProjectMember.project_id).filter(
ProjectMember.user_id == current_user.id
).all()
project_ids = [pid[0] for pid in project_ids]
if not project_ids and not current_user.is_admin:
return []
# For non-admin users, filter by their project access
if not current_user.is_admin:
# Get activities from user's projects, excluding deleted records
all_activities = []
for project_id in project_ids:
activities = ActivityService.get_activities_excluding_deleted(
db=db,
project_id=project_id,
skip=0,
limit=limit * 2 # Get more to account for filtering
)
all_activities.extend(activities)
# Sort by created_at and apply pagination
all_activities.sort(key=lambda x: x.created_at, reverse=True)
return all_activities[skip:skip + limit]
else:
# Admin gets all activities excluding deleted records
# Only artists are restricted to their explicit project memberships; coordinators,
# directors, developers, and admins see recent activity across all projects
# (matches shots.py/assets.py).
if current_user.role != UserRole.ARTIST:
activities = ActivityService.get_activities_excluding_deleted(
db=db,
skip=skip,
@@ -157,6 +137,30 @@ def get_recent_activities(
)
return activities
# Get all projects the artist is a member of
project_ids = db.query(ProjectMember.project_id).filter(
ProjectMember.user_id == current_user.id
).all()
project_ids = [pid[0] for pid in project_ids]
if not project_ids:
return []
all_activities = []
for project_id in project_ids:
activities = ActivityService.get_activities_excluding_deleted(
db=db,
project_id=project_id,
skip=0,
limit=limit * 2 # Get more to account for filtering
)
all_activities.extend(activities)
# Sort by created_at and apply pagination
all_activities.sort(key=lambda x: x.created_at, reverse=True)
return all_activities[skip:skip + limit]
# Admin-only endpoints that include activities for deleted records
@router.get("/admin/project/{project_id}/all", response_model=List[ActivityResponse])