Init Repo

This commit is contained in:
2026-02-28 03:22:04 +08:00
commit de59b57ee7
883 changed files with 156857 additions and 0 deletions
+397
View File
@@ -0,0 +1,397 @@
from typing import Optional, Dict, Any
from sqlalchemy.orm import Session
from models.activity import Activity, ActivityType
from models.user import User
from models.task import Task
import logging
logger = logging.getLogger(__name__)
class ActivityService:
"""Service for creating and managing activity logs."""
@staticmethod
def log_activity(
db: Session,
type: ActivityType,
user_id: int,
description: str,
project_id: Optional[int] = None,
task_id: Optional[int] = None,
asset_id: Optional[int] = None,
shot_id: Optional[int] = None,
submission_id: Optional[int] = None,
activity_metadata: Optional[Dict[str, Any]] = None
) -> Optional[Activity]:
"""Log an activity to the database."""
try:
activity = Activity(
type=type,
user_id=user_id,
description=description,
project_id=project_id,
task_id=task_id,
asset_id=asset_id,
shot_id=shot_id,
submission_id=submission_id,
activity_metadata=activity_metadata
)
db.add(activity)
db.commit()
db.refresh(activity)
logger.info(f"Activity logged: {type} by user {user_id}")
return activity
except Exception as e:
logger.error(f"Failed to log activity: {str(e)}")
db.rollback()
return None
@staticmethod
def log_task_created(db: Session, task: Task, user: User):
"""Log task creation activity."""
description = f"{user.first_name} {user.last_name} created task '{task.name}'"
ActivityService.log_activity(
db=db,
type=ActivityType.TASK_CREATED,
user_id=user.id,
description=description,
project_id=task.project_id,
task_id=task.id,
activity_metadata={"task_type": task.task_type, "status": task.status}
)
@staticmethod
def log_task_assigned(db: Session, task: Task, assigned_user: User, assigner: User):
"""Log task assignment activity."""
description = f"{assigner.first_name} {assigner.last_name} assigned task '{task.name}' to {assigned_user.first_name} {assigned_user.last_name}"
ActivityService.log_activity(
db=db,
type=ActivityType.TASK_ASSIGNED,
user_id=assigner.id,
description=description,
project_id=task.project_id,
task_id=task.id,
activity_metadata={"assigned_to": assigned_user.id}
)
@staticmethod
def log_task_status_changed(db: Session, task: Task, old_status: str, new_status: str, user: User):
"""Log task status change activity."""
description = f"{user.first_name} {user.last_name} changed task '{task.name}' status from {old_status} to {new_status}"
ActivityService.log_activity(
db=db,
type=ActivityType.TASK_STATUS_CHANGED,
user_id=user.id,
description=description,
project_id=task.project_id,
task_id=task.id,
activity_metadata={"old_status": old_status, "new_status": new_status}
)
@staticmethod
def log_submission_created(db: Session, submission, user: User):
"""Log submission creation activity."""
description = f"{user.first_name} {user.last_name} submitted work for task '{submission.task.name}' (Version {submission.version_number})"
ActivityService.log_activity(
db=db,
type=ActivityType.SUBMISSION_CREATED,
user_id=user.id,
description=description,
project_id=submission.task.project_id,
task_id=submission.task_id,
submission_id=submission.id,
activity_metadata={"version": submission.version_number}
)
@staticmethod
def log_submission_reviewed(db: Session, submission, review, reviewer: User):
"""Log submission review activity."""
decision = "approved" if review.decision == "approved" else "requested retakes for"
description = f"{reviewer.first_name} {reviewer.last_name} {decision} submission for task '{submission.task.name}'"
ActivityService.log_activity(
db=db,
type=ActivityType.SUBMISSION_REVIEWED,
user_id=reviewer.id,
description=description,
project_id=submission.task.project_id,
task_id=submission.task_id,
submission_id=submission.id,
activity_metadata={"decision": review.decision, "has_feedback": bool(review.feedback)}
)
@staticmethod
def log_comment_added(db: Session, task: Task, user: User, comment_text: str):
"""Log comment addition activity."""
preview = comment_text[:50] + "..." if len(comment_text) > 50 else comment_text
description = f"{user.first_name} {user.last_name} commented on task '{task.name}': {preview}"
ActivityService.log_activity(
db=db,
type=ActivityType.COMMENT_ADDED,
user_id=user.id,
description=description,
project_id=task.project_id,
task_id=task.id
)
@staticmethod
def log_asset_created(db: Session, asset, user: User):
"""Log asset creation activity."""
description = f"{user.first_name} {user.last_name} created asset '{asset.name}' ({asset.category})"
ActivityService.log_activity(
db=db,
type=ActivityType.ASSET_CREATED,
user_id=user.id,
description=description,
project_id=asset.project_id,
asset_id=asset.id,
activity_metadata={"category": asset.category}
)
@staticmethod
def log_shot_created(db: Session, shot, user: User):
"""Log shot creation activity."""
description = f"{user.first_name} {user.last_name} created shot '{shot.name}'"
ActivityService.log_activity(
db=db,
type=ActivityType.SHOT_CREATED,
user_id=user.id,
description=description,
project_id=shot.episode.project_id if shot.episode else None,
shot_id=shot.id
)
@staticmethod
def log_project_created(db: Session, project, user: User):
"""Log project creation activity."""
description = f"{user.first_name} {user.last_name} created project '{project.name}'"
ActivityService.log_activity(
db=db,
type=ActivityType.PROJECT_CREATED,
user_id=user.id,
description=description,
project_id=project.id,
activity_metadata={"project_type": project.project_type}
)
@staticmethod
def log_user_joined_project(db: Session, project, user: User, added_by: User):
"""Log user joining project activity."""
description = f"{added_by.first_name} {added_by.last_name} added {user.first_name} {user.last_name} to project '{project.name}'"
ActivityService.log_activity(
db=db,
type=ActivityType.USER_JOINED_PROJECT,
user_id=added_by.id,
description=description,
project_id=project.id,
activity_metadata={"added_user_id": user.id}
)
@staticmethod
def log_shot_soft_deletion(db: Session, shot, user: User, deletion_info: Dict[str, Any]):
"""Log shot soft deletion activity."""
description = f"{user.first_name} {user.last_name} deleted shot '{shot.name}' from episode '{deletion_info.get('episode_name', 'Unknown')}'"
ActivityService.log_activity(
db=db,
type=ActivityType.SHOT_DELETED,
user_id=user.id,
description=description,
project_id=deletion_info.get('project_id'),
shot_id=shot.id,
activity_metadata={
"shot_name": shot.name,
"episode_name": deletion_info.get('episode_name'),
"project_name": deletion_info.get('project_name'),
"task_count": deletion_info.get('task_count', 0),
"submission_count": deletion_info.get('submission_count', 0),
"attachment_count": deletion_info.get('attachment_count', 0),
"note_count": deletion_info.get('note_count', 0),
"review_count": deletion_info.get('review_count', 0),
"affected_users": deletion_info.get('affected_users', [])
}
)
@staticmethod
def log_asset_soft_deletion(db: Session, asset, user: User, deletion_info: Dict[str, Any]):
"""Log asset soft deletion activity."""
description = f"{user.first_name} {user.last_name} deleted asset '{asset.name}' ({asset.category})"
ActivityService.log_activity(
db=db,
type=ActivityType.ASSET_DELETED,
user_id=user.id,
description=description,
project_id=asset.project_id,
asset_id=asset.id,
activity_metadata={
"asset_name": asset.name,
"asset_category": asset.category,
"project_name": deletion_info.get('project_name'),
"task_count": deletion_info.get('task_count', 0),
"submission_count": deletion_info.get('submission_count', 0),
"attachment_count": deletion_info.get('attachment_count', 0),
"note_count": deletion_info.get('note_count', 0),
"review_count": deletion_info.get('review_count', 0),
"affected_users": deletion_info.get('affected_users', [])
}
)
@staticmethod
def log_shot_recovery(db: Session, shot, user: User, recovery_info: Dict[str, Any]):
"""Log shot recovery activity."""
description = f"{user.first_name} {user.last_name} recovered shot '{shot.name}' from episode '{recovery_info.get('episode_name', 'Unknown')}'"
ActivityService.log_activity(
db=db,
type=ActivityType.SHOT_RECOVERED,
user_id=user.id,
description=description,
project_id=recovery_info.get('project_id'),
shot_id=shot.id,
activity_metadata={
"shot_name": shot.name,
"episode_name": recovery_info.get('episode_name'),
"project_name": recovery_info.get('project_name'),
"recovered_tasks": recovery_info.get('recovered_tasks', 0),
"recovered_submissions": recovery_info.get('recovered_submissions', 0),
"recovered_attachments": recovery_info.get('recovered_attachments', 0),
"recovered_notes": recovery_info.get('recovered_notes', 0),
"recovered_reviews": recovery_info.get('recovered_reviews', 0),
"deleted_at": recovery_info.get('deleted_at'),
"deleted_by": recovery_info.get('deleted_by')
}
)
@staticmethod
def log_asset_recovery(db: Session, asset, user: User, recovery_info: Dict[str, Any]):
"""Log asset recovery activity."""
description = f"{user.first_name} {user.last_name} recovered asset '{asset.name}' ({asset.category})"
ActivityService.log_activity(
db=db,
type=ActivityType.ASSET_RECOVERED,
user_id=user.id,
description=description,
project_id=asset.project_id,
asset_id=asset.id,
activity_metadata={
"asset_name": asset.name,
"asset_category": asset.category,
"project_name": recovery_info.get('project_name'),
"recovered_tasks": recovery_info.get('recovered_tasks', 0),
"recovered_submissions": recovery_info.get('recovered_submissions', 0),
"recovered_attachments": recovery_info.get('recovered_attachments', 0),
"recovered_notes": recovery_info.get('recovered_notes', 0),
"recovered_reviews": recovery_info.get('recovered_reviews', 0),
"deleted_at": recovery_info.get('deleted_at'),
"deleted_by": recovery_info.get('deleted_by')
}
)
@staticmethod
def get_activities_excluding_deleted(
db: Session,
project_id: Optional[int] = None,
task_id: Optional[int] = None,
user_id: Optional[int] = None,
skip: int = 0,
limit: int = 50,
type_filter: Optional[ActivityType] = None,
days: Optional[int] = None
):
"""Get activities excluding those related to deleted records."""
from sqlalchemy import and_, or_, desc
from datetime import datetime, timedelta
from models.task import Task
from models.shot import Shot
from models.asset import Asset
from models.submission import Submission
query = db.query(Activity)
# Base filters
if project_id:
query = query.filter(Activity.project_id == project_id)
if task_id:
query = query.filter(Activity.task_id == task_id)
if user_id:
query = query.filter(Activity.user_id == user_id)
if type_filter:
query = query.filter(Activity.type == type_filter)
if days:
cutoff_date = datetime.utcnow() - timedelta(days=days)
query = query.filter(Activity.created_at >= cutoff_date)
# Exclude activities related to deleted records
# For task-related activities, exclude if task is deleted
task_subquery = db.query(Task.id).filter(Task.deleted_at.is_(None)).subquery()
# For shot-related activities, exclude if shot is deleted
shot_subquery = db.query(Shot.id).filter(Shot.deleted_at.is_(None)).subquery()
# For asset-related activities, exclude if asset is deleted
asset_subquery = db.query(Asset.id).filter(Asset.deleted_at.is_(None)).subquery()
# For submission-related activities, exclude if submission is deleted
submission_subquery = db.query(Submission.id).filter(Submission.deleted_at.is_(None)).subquery()
# Apply exclusion filters
query = query.filter(
or_(
Activity.task_id.is_(None),
Activity.task_id.in_(task_subquery)
)
).filter(
or_(
Activity.shot_id.is_(None),
Activity.shot_id.in_(shot_subquery)
)
).filter(
or_(
Activity.asset_id.is_(None),
Activity.asset_id.in_(asset_subquery)
)
).filter(
or_(
Activity.submission_id.is_(None),
Activity.submission_id.in_(submission_subquery)
)
)
return query.order_by(desc(Activity.created_at)).offset(skip).limit(limit).all()
@staticmethod
def get_activities_including_deleted(
db: Session,
project_id: Optional[int] = None,
task_id: Optional[int] = None,
user_id: Optional[int] = None,
skip: int = 0,
limit: int = 50,
type_filter: Optional[ActivityType] = None,
days: Optional[int] = None
):
"""Get all activities including those related to deleted records (admin only)."""
from sqlalchemy import desc
from datetime import datetime, timedelta
query = db.query(Activity)
# Base filters
if project_id:
query = query.filter(Activity.project_id == project_id)
if task_id:
query = query.filter(Activity.task_id == task_id)
if user_id:
query = query.filter(Activity.user_id == user_id)
if type_filter:
query = query.filter(Activity.type == type_filter)
if days:
cutoff_date = datetime.utcnow() - timedelta(days=days)
query = query.filter(Activity.created_at >= cutoff_date)
return query.order_by(desc(Activity.created_at)).offset(skip).limit(limit).all()
# Global activity service instance
activity_service = ActivityService()