Init Repo
This commit is contained in:
@@ -0,0 +1,295 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from sqlalchemy.orm import Session, joinedload
|
||||
from sqlalchemy import and_
|
||||
from typing import List, Optional
|
||||
|
||||
from database import get_db
|
||||
from models.task import Task, Submission, Review, TaskStatus
|
||||
from models.user import User, UserRole
|
||||
from schemas.task import ReviewCreate, ReviewResponse, SubmissionResponse
|
||||
from utils.auth import get_current_user_from_token, _get_user_from_db
|
||||
from utils.notifications import notification_service
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
def get_current_user(
|
||||
token_data: dict = Depends(get_current_user_from_token),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Get current user with proper database dependency."""
|
||||
return _get_user_from_db(db, token_data["user_id"])
|
||||
|
||||
|
||||
def require_director_coordinator_or_admin(
|
||||
token_data: dict = Depends(get_current_user_from_token),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Dependency to require director, coordinator role, or admin permission."""
|
||||
current_user = _get_user_from_db(db, token_data["user_id"])
|
||||
if (current_user.role not in [UserRole.DIRECTOR, UserRole.COORDINATOR] and
|
||||
not current_user.is_admin):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Director role, Coordinator role, or Admin permission required"
|
||||
)
|
||||
return current_user
|
||||
|
||||
|
||||
def require_role(required_roles: list):
|
||||
"""Create a dependency that requires specific user roles."""
|
||||
def role_checker(
|
||||
token_data: dict = Depends(get_current_user_from_token),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
current_user = _get_user_from_db(db, token_data["user_id"])
|
||||
if current_user.role not in required_roles:
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail="Insufficient permissions"
|
||||
)
|
||||
return current_user
|
||||
return role_checker
|
||||
|
||||
|
||||
@router.get("/pending", response_model=List[SubmissionResponse])
|
||||
async def get_pending_reviews(
|
||||
project_id: Optional[int] = Query(None, description="Filter by project ID"),
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(100, ge=1, le=1000),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_director_coordinator_or_admin)
|
||||
):
|
||||
"""Get all submissions pending review. Only directors, coordinators, and users with admin permission can access."""
|
||||
|
||||
# Get submissions that don't have any reviews yet or have retake reviews
|
||||
query = db.query(Submission).options(
|
||||
joinedload(Submission.user),
|
||||
joinedload(Submission.task).joinedload(Task.project),
|
||||
joinedload(Submission.reviews).joinedload("reviewer")
|
||||
).join(Task).filter(
|
||||
Submission.deleted_at.is_(None),
|
||||
Task.deleted_at.is_(None)
|
||||
)
|
||||
|
||||
# Filter by project if specified
|
||||
if project_id:
|
||||
query = query.filter(Task.project_id == project_id)
|
||||
|
||||
# Only get submitted tasks
|
||||
query = query.filter(Task.status == "submitted")
|
||||
|
||||
submissions = query.order_by(Submission.submitted_at.desc()).offset(skip).limit(limit).all()
|
||||
|
||||
# Filter to only include submissions that need review
|
||||
pending_submissions = []
|
||||
for submission in submissions:
|
||||
# Check if submission has any approved reviews
|
||||
has_approved_review = any(review.decision == "approved" for review in submission.reviews)
|
||||
|
||||
if not has_approved_review:
|
||||
# Get latest review
|
||||
latest_review = None
|
||||
if submission.reviews:
|
||||
latest_review_obj = max(submission.reviews, key=lambda r: r.reviewed_at)
|
||||
latest_review = {
|
||||
"id": latest_review_obj.id,
|
||||
"submission_id": latest_review_obj.submission_id,
|
||||
"reviewer_id": latest_review_obj.reviewer_id,
|
||||
"decision": latest_review_obj.decision,
|
||||
"feedback": latest_review_obj.feedback,
|
||||
"reviewed_at": latest_review_obj.reviewed_at,
|
||||
"reviewer_first_name": latest_review_obj.reviewer.first_name,
|
||||
"reviewer_last_name": latest_review_obj.reviewer.last_name
|
||||
}
|
||||
|
||||
submission_data = {
|
||||
"id": submission.id,
|
||||
"task_id": submission.task_id,
|
||||
"user_id": submission.user_id,
|
||||
"file_path": submission.file_path,
|
||||
"file_name": submission.file_name,
|
||||
"version_number": submission.version_number,
|
||||
"notes": submission.notes,
|
||||
"submitted_at": submission.submitted_at,
|
||||
"user_first_name": submission.user.first_name,
|
||||
"user_last_name": submission.user.last_name,
|
||||
"latest_review": latest_review
|
||||
}
|
||||
pending_submissions.append(SubmissionResponse(**submission_data))
|
||||
|
||||
return pending_submissions
|
||||
|
||||
|
||||
@router.post("/{submission_id}/approve", response_model=ReviewResponse)
|
||||
async def approve_submission(
|
||||
submission_id: int,
|
||||
review: ReviewCreate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_director_coordinator_or_admin)
|
||||
):
|
||||
"""Approve a submission. Only directors, coordinators, and users with admin permission can approve."""
|
||||
|
||||
submission = db.query(Submission).options(
|
||||
joinedload(Submission.task)
|
||||
).filter(
|
||||
Submission.id == submission_id,
|
||||
Submission.deleted_at.is_(None)
|
||||
).first()
|
||||
|
||||
if not submission:
|
||||
raise HTTPException(status_code=404, detail="Submission not found")
|
||||
|
||||
# Check if submission is in submitted state
|
||||
if submission.task.status != "submitted":
|
||||
raise HTTPException(status_code=400, detail="Submission is not in submitted state")
|
||||
|
||||
# Force decision to approved
|
||||
review.decision = "approved"
|
||||
|
||||
# Create review record
|
||||
db_review = Review(
|
||||
submission_id=submission_id,
|
||||
reviewer_id=current_user.id,
|
||||
decision=review.decision,
|
||||
feedback=review.feedback
|
||||
)
|
||||
db.add(db_review)
|
||||
|
||||
# Update task status to approved
|
||||
submission.task.status = "approved"
|
||||
|
||||
db.commit()
|
||||
db.refresh(db_review)
|
||||
|
||||
# Send notification to artist
|
||||
notification_service.notify_submission_reviewed(db, submission, db_review, current_user)
|
||||
|
||||
# Load reviewer information for response
|
||||
db_review = db.query(Review).options(
|
||||
joinedload(Review.reviewer)
|
||||
).filter(Review.id == db_review.id).first()
|
||||
|
||||
review_data = {
|
||||
"id": db_review.id,
|
||||
"submission_id": db_review.submission_id,
|
||||
"reviewer_id": db_review.reviewer_id,
|
||||
"decision": db_review.decision,
|
||||
"feedback": db_review.feedback,
|
||||
"reviewed_at": db_review.reviewed_at,
|
||||
"reviewer_first_name": db_review.reviewer.first_name,
|
||||
"reviewer_last_name": db_review.reviewer.last_name
|
||||
}
|
||||
|
||||
return ReviewResponse(**review_data)
|
||||
|
||||
|
||||
@router.post("/{submission_id}/retake", response_model=ReviewResponse)
|
||||
async def request_retake(
|
||||
submission_id: int,
|
||||
review: ReviewCreate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_director_coordinator_or_admin)
|
||||
):
|
||||
"""Request a retake for a submission. Only directors, coordinators, and users with admin permission can request retakes."""
|
||||
|
||||
submission = db.query(Submission).options(
|
||||
joinedload(Submission.task)
|
||||
).filter(
|
||||
Submission.id == submission_id,
|
||||
Submission.deleted_at.is_(None)
|
||||
).first()
|
||||
|
||||
if not submission:
|
||||
raise HTTPException(status_code=404, detail="Submission not found")
|
||||
|
||||
# Check if submission is in submitted state
|
||||
if submission.task.status != "submitted":
|
||||
raise HTTPException(status_code=400, detail="Submission is not in submitted state")
|
||||
|
||||
# Force decision to retake and require feedback
|
||||
review.decision = "retake"
|
||||
if not review.feedback or review.feedback.strip() == "":
|
||||
raise HTTPException(status_code=400, detail="Feedback is required when requesting a retake")
|
||||
|
||||
# Create review record
|
||||
db_review = Review(
|
||||
submission_id=submission_id,
|
||||
reviewer_id=current_user.id,
|
||||
decision=review.decision,
|
||||
feedback=review.feedback
|
||||
)
|
||||
db.add(db_review)
|
||||
|
||||
# Update task status to retake
|
||||
submission.task.status = "retake"
|
||||
|
||||
db.commit()
|
||||
db.refresh(db_review)
|
||||
|
||||
# Send notification to artist
|
||||
notification_service.notify_submission_reviewed(db, submission, db_review, current_user)
|
||||
|
||||
# Load reviewer information for response
|
||||
db_review = db.query(Review).options(
|
||||
joinedload(Review.reviewer)
|
||||
).filter(Review.id == db_review.id).first()
|
||||
|
||||
review_data = {
|
||||
"id": db_review.id,
|
||||
"submission_id": db_review.submission_id,
|
||||
"reviewer_id": db_review.reviewer_id,
|
||||
"decision": db_review.decision,
|
||||
"feedback": db_review.feedback,
|
||||
"reviewed_at": db_review.reviewed_at,
|
||||
"reviewer_first_name": db_review.reviewer.first_name,
|
||||
"reviewer_last_name": db_review.reviewer.last_name
|
||||
}
|
||||
|
||||
return ReviewResponse(**review_data)
|
||||
|
||||
|
||||
@router.get("/{submission_id}/reviews", response_model=List[ReviewResponse])
|
||||
async def get_submission_reviews(
|
||||
submission_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Get all reviews for a submission."""
|
||||
|
||||
submission = db.query(Submission).options(
|
||||
joinedload(Submission.task)
|
||||
).filter(
|
||||
Submission.id == submission_id,
|
||||
Submission.deleted_at.is_(None)
|
||||
).first()
|
||||
|
||||
if not submission:
|
||||
raise HTTPException(status_code=404, detail="Submission not found")
|
||||
|
||||
# Artists can only view reviews for their own submissions
|
||||
if current_user.role == UserRole.ARTIST and submission.user_id != current_user.id:
|
||||
raise HTTPException(status_code=403, detail="Not authorized to view reviews for this submission")
|
||||
|
||||
reviews = db.query(Review).options(
|
||||
joinedload(Review.reviewer)
|
||||
).filter(
|
||||
Review.submission_id == submission_id,
|
||||
Review.deleted_at.is_(None)
|
||||
).order_by(Review.reviewed_at.desc()).all()
|
||||
|
||||
result = []
|
||||
for review in reviews:
|
||||
review_data = {
|
||||
"id": review.id,
|
||||
"submission_id": review.submission_id,
|
||||
"reviewer_id": review.reviewer_id,
|
||||
"decision": review.decision,
|
||||
"feedback": review.feedback,
|
||||
"reviewed_at": review.reviewed_at,
|
||||
"reviewer_first_name": review.reviewer.first_name,
|
||||
"reviewer_last_name": review.reviewer.last_name
|
||||
}
|
||||
result.append(ReviewResponse(**review_data))
|
||||
|
||||
return result
|
||||
Reference in New Issue
Block a user