Init Repo
This commit is contained in:
@@ -0,0 +1,800 @@
|
||||
"""
|
||||
Admin Router
|
||||
|
||||
This router contains admin-only endpoints for managing soft-deleted data
|
||||
and other administrative functions.
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List, Optional
|
||||
from pydantic import BaseModel
|
||||
import time
|
||||
from collections import defaultdict
|
||||
|
||||
from database import get_db
|
||||
from models.user import User, UserRole
|
||||
from utils.auth import get_current_user_from_token
|
||||
from services.recovery_service import RecoveryService
|
||||
from services.batch_operations import BatchOperationsService
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
# Simple rate limiting storage (in production, use Redis or similar)
|
||||
_rate_limit_storage = defaultdict(list)
|
||||
PERMANENT_DELETE_RATE_LIMIT = 10 # Max 10 permanent delete operations per minute per user
|
||||
|
||||
|
||||
class BulkRecoveryRequest(BaseModel):
|
||||
shot_ids: List[int] = []
|
||||
asset_ids: List[int] = []
|
||||
|
||||
|
||||
class BulkDeletionRequest(BaseModel):
|
||||
shot_ids: List[int] = []
|
||||
asset_ids: List[int] = []
|
||||
batch_size: Optional[int] = 50
|
||||
|
||||
|
||||
class BatchPreviewRequest(BaseModel):
|
||||
shot_ids: List[int] = []
|
||||
asset_ids: List[int] = []
|
||||
|
||||
|
||||
class PermanentDeleteRequest(BaseModel):
|
||||
confirmation_token: str
|
||||
|
||||
|
||||
class BulkPermanentDeleteRequest(BaseModel):
|
||||
shot_ids: List[int] = []
|
||||
asset_ids: List[int] = []
|
||||
confirmation_token: str
|
||||
|
||||
|
||||
def require_admin(
|
||||
token_data: dict = Depends(get_current_user_from_token),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Require admin role."""
|
||||
from utils.auth import _get_user_from_db
|
||||
current_user = _get_user_from_db(db, token_data["user_id"])
|
||||
|
||||
if not current_user.is_admin:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Admin permission required"
|
||||
)
|
||||
return current_user
|
||||
|
||||
|
||||
def check_permanent_delete_rate_limit(user_id: int):
|
||||
"""Check if user has exceeded permanent delete rate limit."""
|
||||
current_time = time.time()
|
||||
user_requests = _rate_limit_storage[user_id]
|
||||
|
||||
# Remove requests older than 1 minute
|
||||
user_requests[:] = [req_time for req_time in user_requests if current_time - req_time < 60]
|
||||
|
||||
if len(user_requests) >= PERMANENT_DELETE_RATE_LIMIT:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
|
||||
detail=f"Rate limit exceeded. Maximum {PERMANENT_DELETE_RATE_LIMIT} permanent delete operations per minute."
|
||||
)
|
||||
|
||||
# Add current request
|
||||
user_requests.append(current_time)
|
||||
|
||||
|
||||
def validate_confirmation_token(token: str, expected_action: str):
|
||||
"""Validate confirmation token for permanent delete operations."""
|
||||
# Simple token validation - in production, use proper token generation/validation
|
||||
expected_token = f"CONFIRM_{expected_action}_PERMANENT_DELETE"
|
||||
if token != expected_token:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Invalid confirmation token. Permanent deletion requires explicit confirmation."
|
||||
)
|
||||
|
||||
|
||||
@router.get("/deleted-shots/")
|
||||
async def get_deleted_shots(
|
||||
project_id: Optional[int] = Query(None, description="Filter by project ID"),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_admin)
|
||||
):
|
||||
"""Get list of deleted shots for admin recovery interface"""
|
||||
recovery_service = RecoveryService()
|
||||
deleted_shots = recovery_service.get_deleted_shots(project_id, db)
|
||||
|
||||
return [
|
||||
{
|
||||
"id": shot.id,
|
||||
"name": shot.name,
|
||||
"episode_name": shot.episode_name,
|
||||
"project_id": shot.project_id,
|
||||
"project_name": shot.project_name,
|
||||
"deleted_at": shot.deleted_at,
|
||||
"deleted_by": shot.deleted_by,
|
||||
"deleted_by_name": shot.deleted_by_name,
|
||||
"task_count": shot.task_count,
|
||||
"submission_count": shot.submission_count,
|
||||
"attachment_count": shot.attachment_count,
|
||||
"note_count": shot.note_count,
|
||||
"review_count": shot.review_count
|
||||
}
|
||||
for shot in deleted_shots
|
||||
]
|
||||
|
||||
|
||||
@router.get("/deleted-assets/")
|
||||
async def get_deleted_assets(
|
||||
project_id: Optional[int] = Query(None, description="Filter by project ID"),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_admin)
|
||||
):
|
||||
"""Get list of deleted assets for admin recovery interface"""
|
||||
recovery_service = RecoveryService()
|
||||
deleted_assets = recovery_service.get_deleted_assets(project_id, db)
|
||||
|
||||
return [
|
||||
{
|
||||
"id": asset.id,
|
||||
"name": asset.name,
|
||||
"category": asset.category,
|
||||
"project_id": asset.project_id,
|
||||
"project_name": asset.project_name,
|
||||
"deleted_at": asset.deleted_at,
|
||||
"deleted_by": asset.deleted_by,
|
||||
"deleted_by_name": asset.deleted_by_name,
|
||||
"task_count": asset.task_count,
|
||||
"submission_count": asset.submission_count,
|
||||
"attachment_count": asset.attachment_count,
|
||||
"note_count": asset.note_count,
|
||||
"review_count": asset.review_count
|
||||
}
|
||||
for asset in deleted_assets
|
||||
]
|
||||
|
||||
|
||||
@router.get("/shots/{shot_id}/recovery-preview")
|
||||
async def get_shot_recovery_preview(
|
||||
shot_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_admin)
|
||||
):
|
||||
"""Get information about what will be recovered when restoring a shot"""
|
||||
recovery_service = RecoveryService()
|
||||
recovery_info = recovery_service.preview_shot_recovery(shot_id, db)
|
||||
|
||||
if not recovery_info:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Deleted shot not found"
|
||||
)
|
||||
|
||||
return {
|
||||
"shot_id": recovery_info.shot_id,
|
||||
"name": recovery_info.name,
|
||||
"episode_name": recovery_info.episode_name,
|
||||
"project_id": recovery_info.project_id,
|
||||
"project_name": recovery_info.project_name,
|
||||
"task_count": recovery_info.task_count,
|
||||
"submission_count": recovery_info.submission_count,
|
||||
"attachment_count": recovery_info.attachment_count,
|
||||
"note_count": recovery_info.note_count,
|
||||
"review_count": recovery_info.review_count,
|
||||
"deleted_at": recovery_info.deleted_at,
|
||||
"deleted_by": recovery_info.deleted_by,
|
||||
"deleted_by_name": recovery_info.deleted_by_name,
|
||||
"files_preserved": recovery_info.files_preserved,
|
||||
"file_count": recovery_info.file_count
|
||||
}
|
||||
|
||||
|
||||
@router.get("/assets/{asset_id}/recovery-preview")
|
||||
async def get_asset_recovery_preview(
|
||||
asset_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_admin)
|
||||
):
|
||||
"""Get information about what will be recovered when restoring an asset"""
|
||||
recovery_service = RecoveryService()
|
||||
recovery_info = recovery_service.preview_asset_recovery(asset_id, db)
|
||||
|
||||
if not recovery_info:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Deleted asset not found"
|
||||
)
|
||||
|
||||
return {
|
||||
"asset_id": recovery_info.asset_id,
|
||||
"name": recovery_info.name,
|
||||
"project_name": recovery_info.project_name,
|
||||
"task_count": recovery_info.task_count,
|
||||
"submission_count": recovery_info.submission_count,
|
||||
"attachment_count": recovery_info.attachment_count,
|
||||
"note_count": recovery_info.note_count,
|
||||
"review_count": recovery_info.review_count,
|
||||
"deleted_at": recovery_info.deleted_at,
|
||||
"deleted_by": recovery_info.deleted_by,
|
||||
"deleted_by_name": recovery_info.deleted_by_name,
|
||||
"files_preserved": recovery_info.files_preserved,
|
||||
"file_count": recovery_info.file_count
|
||||
}
|
||||
|
||||
|
||||
@router.post("/shots/{shot_id}/recover")
|
||||
async def recover_shot(
|
||||
shot_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_admin)
|
||||
):
|
||||
"""Recover a soft-deleted shot and all its related data"""
|
||||
recovery_service = RecoveryService()
|
||||
result = recovery_service.recover_shot(shot_id, db, current_user)
|
||||
|
||||
if not result.success:
|
||||
db.rollback()
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail={
|
||||
"message": "Failed to recover shot",
|
||||
"errors": result.errors
|
||||
}
|
||||
)
|
||||
|
||||
# Commit the transaction
|
||||
db.commit()
|
||||
|
||||
return {
|
||||
"message": f"Shot '{result.name}' and all related data have been recovered",
|
||||
"shot_id": result.shot_id,
|
||||
"name": result.name,
|
||||
"recovered_at": result.recovered_at,
|
||||
"recovered_by": result.recovered_by,
|
||||
"recovered_tasks": result.recovered_tasks,
|
||||
"recovered_submissions": result.recovered_submissions,
|
||||
"recovered_attachments": result.recovered_attachments,
|
||||
"recovered_notes": result.recovered_notes,
|
||||
"recovered_reviews": result.recovered_reviews,
|
||||
"operation_duration": result.operation_duration
|
||||
}
|
||||
|
||||
|
||||
@router.post("/assets/{asset_id}/recover")
|
||||
async def recover_asset(
|
||||
asset_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_admin)
|
||||
):
|
||||
"""Recover a soft-deleted asset and all its related data"""
|
||||
recovery_service = RecoveryService()
|
||||
result = recovery_service.recover_asset(asset_id, db, current_user)
|
||||
|
||||
if not result.success:
|
||||
db.rollback()
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail={
|
||||
"message": "Failed to recover asset",
|
||||
"errors": result.errors
|
||||
}
|
||||
)
|
||||
|
||||
# Commit the transaction
|
||||
db.commit()
|
||||
|
||||
return {
|
||||
"message": f"Asset '{result.name}' and all related data have been recovered",
|
||||
"asset_id": result.asset_id,
|
||||
"name": result.name,
|
||||
"recovered_at": result.recovered_at,
|
||||
"recovered_by": result.recovered_by,
|
||||
"recovered_tasks": result.recovered_tasks,
|
||||
"recovered_submissions": result.recovered_submissions,
|
||||
"recovered_attachments": result.recovered_attachments,
|
||||
"recovered_notes": result.recovered_notes,
|
||||
"recovered_reviews": result.recovered_reviews,
|
||||
"operation_duration": result.operation_duration
|
||||
}
|
||||
|
||||
|
||||
@router.post("/shots/bulk-recover")
|
||||
async def bulk_recover_shots(
|
||||
request: BulkRecoveryRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_admin)
|
||||
):
|
||||
"""Bulk recover multiple shots"""
|
||||
if not request.shot_ids:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="No shot IDs provided"
|
||||
)
|
||||
|
||||
recovery_service = RecoveryService()
|
||||
result = recovery_service.bulk_recover_shots(request.shot_ids, db, current_user)
|
||||
|
||||
# Commit the transaction
|
||||
db.commit()
|
||||
|
||||
return {
|
||||
"total_items": result.total_items,
|
||||
"successful_recoveries": result.successful_recoveries,
|
||||
"failed_recoveries": result.failed_recoveries,
|
||||
"results": [
|
||||
{
|
||||
"success": r.success,
|
||||
"shot_id": r.shot_id,
|
||||
"name": r.name,
|
||||
"recovered_tasks": r.recovered_tasks,
|
||||
"recovered_submissions": r.recovered_submissions,
|
||||
"recovered_attachments": r.recovered_attachments,
|
||||
"recovered_notes": r.recovered_notes,
|
||||
"recovered_reviews": r.recovered_reviews,
|
||||
"operation_duration": r.operation_duration,
|
||||
"errors": r.errors,
|
||||
"warnings": r.warnings
|
||||
}
|
||||
for r in result.results
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"item_id": e.item_id,
|
||||
"item_type": e.item_type,
|
||||
"error": e.error
|
||||
}
|
||||
for e in result.errors
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@router.post("/assets/bulk-recover")
|
||||
async def bulk_recover_assets(
|
||||
request: BulkRecoveryRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_admin)
|
||||
):
|
||||
"""Bulk recover multiple assets"""
|
||||
if not request.asset_ids:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="No asset IDs provided"
|
||||
)
|
||||
|
||||
recovery_service = RecoveryService()
|
||||
result = recovery_service.bulk_recover_assets(request.asset_ids, db, current_user)
|
||||
|
||||
# Commit the transaction
|
||||
db.commit()
|
||||
|
||||
return {
|
||||
"total_items": result.total_items,
|
||||
"successful_recoveries": result.successful_recoveries,
|
||||
"failed_recoveries": result.failed_recoveries,
|
||||
"results": [
|
||||
{
|
||||
"success": r.success,
|
||||
"asset_id": r.asset_id,
|
||||
"name": r.name,
|
||||
"recovered_tasks": r.recovered_tasks,
|
||||
"recovered_submissions": r.recovered_submissions,
|
||||
"recovered_attachments": r.recovered_attachments,
|
||||
"recovered_notes": r.recovered_notes,
|
||||
"recovered_reviews": r.recovered_reviews,
|
||||
"operation_duration": r.operation_duration,
|
||||
"errors": r.errors,
|
||||
"warnings": r.warnings
|
||||
}
|
||||
for r in result.results
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"item_id": e.item_id,
|
||||
"item_type": e.item_type,
|
||||
"error": e.error
|
||||
}
|
||||
for e in result.errors
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@router.get("/recovery-stats/")
|
||||
async def get_recovery_stats(
|
||||
project_id: Optional[int] = Query(None, description="Filter by project ID"),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_admin)
|
||||
):
|
||||
"""Get recovery statistics for admin dashboard"""
|
||||
recovery_service = RecoveryService()
|
||||
stats = recovery_service.get_recovery_stats(project_id, db)
|
||||
|
||||
return {
|
||||
"deleted_shots_count": stats.deleted_shots_count,
|
||||
"deleted_assets_count": stats.deleted_assets_count,
|
||||
"total_deleted_tasks": stats.total_deleted_tasks,
|
||||
"total_deleted_files": stats.total_deleted_files,
|
||||
"oldest_deletion_date": stats.oldest_deletion_date
|
||||
}
|
||||
|
||||
|
||||
# Permanent Delete Endpoints
|
||||
|
||||
@router.delete("/shots/{shot_id}/permanent")
|
||||
async def permanent_delete_shot(
|
||||
shot_id: int,
|
||||
request: PermanentDeleteRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_admin)
|
||||
):
|
||||
"""Permanently delete a soft-deleted shot and all its related data"""
|
||||
# Check rate limit
|
||||
check_permanent_delete_rate_limit(current_user.id)
|
||||
|
||||
# Validate confirmation token
|
||||
validate_confirmation_token(request.confirmation_token, "SHOT")
|
||||
|
||||
recovery_service = RecoveryService()
|
||||
result = recovery_service.permanent_delete_shot(shot_id, db, current_user)
|
||||
|
||||
if not result.success:
|
||||
db.rollback()
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail={
|
||||
"message": "Failed to permanently delete shot",
|
||||
"errors": result.errors,
|
||||
"warnings": result.warnings
|
||||
}
|
||||
)
|
||||
|
||||
# Commit the transaction
|
||||
db.commit()
|
||||
|
||||
return {
|
||||
"message": f"Shot '{result.name}' has been permanently deleted",
|
||||
"shot_id": result.shot_id,
|
||||
"name": result.name,
|
||||
"deleted_at": result.deleted_at,
|
||||
"deleted_by": result.deleted_by,
|
||||
"deleted_tasks": result.deleted_tasks,
|
||||
"deleted_submissions": result.deleted_submissions,
|
||||
"deleted_attachments": result.deleted_attachments,
|
||||
"deleted_notes": result.deleted_notes,
|
||||
"deleted_reviews": result.deleted_reviews,
|
||||
"deleted_files": result.deleted_files,
|
||||
"database_records_deleted": result.database_records_deleted,
|
||||
"operation_duration": result.operation_duration,
|
||||
"warnings": result.warnings
|
||||
}
|
||||
|
||||
|
||||
@router.delete("/assets/{asset_id}/permanent")
|
||||
async def permanent_delete_asset(
|
||||
asset_id: int,
|
||||
request: PermanentDeleteRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_admin)
|
||||
):
|
||||
"""Permanently delete a soft-deleted asset and all its related data"""
|
||||
# Check rate limit
|
||||
check_permanent_delete_rate_limit(current_user.id)
|
||||
|
||||
# Validate confirmation token
|
||||
validate_confirmation_token(request.confirmation_token, "ASSET")
|
||||
|
||||
recovery_service = RecoveryService()
|
||||
result = recovery_service.permanent_delete_asset(asset_id, db, current_user)
|
||||
|
||||
if not result.success:
|
||||
db.rollback()
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail={
|
||||
"message": "Failed to permanently delete asset",
|
||||
"errors": result.errors,
|
||||
"warnings": result.warnings
|
||||
}
|
||||
)
|
||||
|
||||
# Commit the transaction
|
||||
db.commit()
|
||||
|
||||
return {
|
||||
"message": f"Asset '{result.name}' has been permanently deleted",
|
||||
"asset_id": result.asset_id,
|
||||
"name": result.name,
|
||||
"deleted_at": result.deleted_at,
|
||||
"deleted_by": result.deleted_by,
|
||||
"deleted_tasks": result.deleted_tasks,
|
||||
"deleted_submissions": result.deleted_submissions,
|
||||
"deleted_attachments": result.deleted_attachments,
|
||||
"deleted_notes": result.deleted_notes,
|
||||
"deleted_reviews": result.deleted_reviews,
|
||||
"deleted_files": result.deleted_files,
|
||||
"database_records_deleted": result.database_records_deleted,
|
||||
"operation_duration": result.operation_duration,
|
||||
"warnings": result.warnings
|
||||
}
|
||||
|
||||
|
||||
@router.delete("/shots/bulk-permanent")
|
||||
async def bulk_permanent_delete_shots(
|
||||
request: BulkPermanentDeleteRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_admin)
|
||||
):
|
||||
"""Permanently delete multiple shots in bulk"""
|
||||
if not request.shot_ids:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="No shot IDs provided"
|
||||
)
|
||||
|
||||
# Check rate limit (bulk operations count as multiple operations)
|
||||
for _ in request.shot_ids:
|
||||
check_permanent_delete_rate_limit(current_user.id)
|
||||
|
||||
# Validate confirmation token
|
||||
validate_confirmation_token(request.confirmation_token, "BULK_SHOTS")
|
||||
|
||||
recovery_service = RecoveryService()
|
||||
result = recovery_service.bulk_permanent_delete_shots(request.shot_ids, db, current_user)
|
||||
|
||||
# Commit the transaction
|
||||
db.commit()
|
||||
|
||||
return {
|
||||
"message": f"Bulk permanent deletion completed: {result.successful_deletions} successful, {result.failed_deletions} failed",
|
||||
"total_items": result.total_items,
|
||||
"successful_deletions": result.successful_deletions,
|
||||
"failed_deletions": result.failed_deletions,
|
||||
"deleted_items": result.deleted_items,
|
||||
"files_deleted": result.files_deleted,
|
||||
"database_records_deleted": result.database_records_deleted,
|
||||
"errors": result.errors
|
||||
}
|
||||
|
||||
|
||||
@router.delete("/assets/bulk-permanent")
|
||||
async def bulk_permanent_delete_assets(
|
||||
request: BulkPermanentDeleteRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_admin)
|
||||
):
|
||||
"""Permanently delete multiple assets in bulk"""
|
||||
if not request.asset_ids:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="No asset IDs provided"
|
||||
)
|
||||
|
||||
# Check rate limit (bulk operations count as multiple operations)
|
||||
for _ in request.asset_ids:
|
||||
check_permanent_delete_rate_limit(current_user.id)
|
||||
|
||||
# Validate confirmation token
|
||||
validate_confirmation_token(request.confirmation_token, "BULK_ASSETS")
|
||||
|
||||
recovery_service = RecoveryService()
|
||||
result = recovery_service.bulk_permanent_delete_assets(request.asset_ids, db, current_user)
|
||||
|
||||
# Commit the transaction
|
||||
db.commit()
|
||||
|
||||
return {
|
||||
"message": f"Bulk permanent deletion completed: {result.successful_deletions} successful, {result.failed_deletions} failed",
|
||||
"total_items": result.total_items,
|
||||
"successful_deletions": result.successful_deletions,
|
||||
"failed_deletions": result.failed_deletions,
|
||||
"deleted_items": result.deleted_items,
|
||||
"files_deleted": result.files_deleted,
|
||||
"database_records_deleted": result.database_records_deleted,
|
||||
"errors": result.errors
|
||||
}
|
||||
|
||||
|
||||
# Batch Operations Endpoints
|
||||
|
||||
@router.post("/batch-deletion-preview")
|
||||
async def get_batch_deletion_preview(
|
||||
request: BatchPreviewRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_admin)
|
||||
):
|
||||
"""Get preview information for a batch deletion operation"""
|
||||
if not request.shot_ids and not request.asset_ids:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="No shot or asset IDs provided"
|
||||
)
|
||||
|
||||
batch_service = BatchOperationsService()
|
||||
preview = batch_service.get_batch_deletion_preview(
|
||||
request.shot_ids, request.asset_ids, db
|
||||
)
|
||||
|
||||
return preview
|
||||
|
||||
|
||||
@router.post("/shots/batch-delete")
|
||||
async def batch_delete_shots(
|
||||
request: BulkDeletionRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_admin)
|
||||
):
|
||||
"""Batch delete multiple shots"""
|
||||
if not request.shot_ids:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="No shot IDs provided"
|
||||
)
|
||||
|
||||
batch_service = BatchOperationsService()
|
||||
result = batch_service.batch_delete_shots(
|
||||
request.shot_ids, db, current_user, request.batch_size or 50
|
||||
)
|
||||
|
||||
# Commit the transaction
|
||||
db.commit()
|
||||
|
||||
return {
|
||||
"total_items": result.total_items,
|
||||
"successful_deletions": result.successful_deletions,
|
||||
"failed_deletions": result.failed_deletions,
|
||||
"operation_duration": result.operation_duration,
|
||||
"total_deleted_tasks": result.total_deleted_tasks,
|
||||
"total_deleted_submissions": result.total_deleted_submissions,
|
||||
"total_deleted_attachments": result.total_deleted_attachments,
|
||||
"total_deleted_notes": result.total_deleted_notes,
|
||||
"total_deleted_reviews": result.total_deleted_reviews,
|
||||
"items": [
|
||||
{
|
||||
"id": item.id,
|
||||
"name": item.name,
|
||||
"type": item.type,
|
||||
"success": item.success,
|
||||
"error": item.error,
|
||||
"deleted_counts": item.deleted_counts
|
||||
}
|
||||
for item in result.items
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@router.post("/assets/batch-delete")
|
||||
async def batch_delete_assets(
|
||||
request: BulkDeletionRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_admin)
|
||||
):
|
||||
"""Batch delete multiple assets"""
|
||||
if not request.asset_ids:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="No asset IDs provided"
|
||||
)
|
||||
|
||||
batch_service = BatchOperationsService()
|
||||
result = batch_service.batch_delete_assets(
|
||||
request.asset_ids, db, current_user, request.batch_size or 50
|
||||
)
|
||||
|
||||
# Commit the transaction
|
||||
db.commit()
|
||||
|
||||
return {
|
||||
"total_items": result.total_items,
|
||||
"successful_deletions": result.successful_deletions,
|
||||
"failed_deletions": result.failed_deletions,
|
||||
"operation_duration": result.operation_duration,
|
||||
"total_deleted_tasks": result.total_deleted_tasks,
|
||||
"total_deleted_submissions": result.total_deleted_submissions,
|
||||
"total_deleted_attachments": result.total_deleted_attachments,
|
||||
"total_deleted_notes": result.total_deleted_notes,
|
||||
"total_deleted_reviews": result.total_deleted_reviews,
|
||||
"items": [
|
||||
{
|
||||
"id": item.id,
|
||||
"name": item.name,
|
||||
"type": item.type,
|
||||
"success": item.success,
|
||||
"error": item.error,
|
||||
"deleted_counts": item.deleted_counts
|
||||
}
|
||||
for item in result.items
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@router.post("/shots/batch-recover")
|
||||
async def batch_recover_shots_enhanced(
|
||||
request: BulkRecoveryRequest,
|
||||
batch_size: Optional[int] = Query(50, description="Batch size for processing"),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_admin)
|
||||
):
|
||||
"""Enhanced batch recover multiple shots with configurable batch size"""
|
||||
if not request.shot_ids:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="No shot IDs provided"
|
||||
)
|
||||
|
||||
batch_service = BatchOperationsService()
|
||||
result = batch_service.batch_recover_shots(
|
||||
request.shot_ids, db, current_user, batch_size
|
||||
)
|
||||
|
||||
# Commit the transaction
|
||||
db.commit()
|
||||
|
||||
return {
|
||||
"total_items": result.total_items,
|
||||
"successful_recoveries": result.successful_recoveries,
|
||||
"failed_recoveries": result.failed_recoveries,
|
||||
"operation_duration": result.operation_duration,
|
||||
"total_recovered_tasks": result.total_recovered_tasks,
|
||||
"total_recovered_submissions": result.total_recovered_submissions,
|
||||
"total_recovered_attachments": result.total_recovered_attachments,
|
||||
"total_recovered_notes": result.total_recovered_notes,
|
||||
"total_recovered_reviews": result.total_recovered_reviews,
|
||||
"items": [
|
||||
{
|
||||
"id": item.id,
|
||||
"name": item.name,
|
||||
"type": item.type,
|
||||
"success": item.success,
|
||||
"error": item.error,
|
||||
"recovered_counts": item.recovered_counts
|
||||
}
|
||||
for item in result.items
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@router.post("/assets/batch-recover")
|
||||
async def batch_recover_assets_enhanced(
|
||||
request: BulkRecoveryRequest,
|
||||
batch_size: Optional[int] = Query(50, description="Batch size for processing"),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_admin)
|
||||
):
|
||||
"""Enhanced batch recover multiple assets with configurable batch size"""
|
||||
if not request.asset_ids:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="No asset IDs provided"
|
||||
)
|
||||
|
||||
batch_service = BatchOperationsService()
|
||||
result = batch_service.batch_recover_assets(
|
||||
request.asset_ids, db, current_user, batch_size
|
||||
)
|
||||
|
||||
# Commit the transaction
|
||||
db.commit()
|
||||
|
||||
return {
|
||||
"total_items": result.total_items,
|
||||
"successful_recoveries": result.successful_recoveries,
|
||||
"failed_recoveries": result.failed_recoveries,
|
||||
"operation_duration": result.operation_duration,
|
||||
"total_recovered_tasks": result.total_recovered_tasks,
|
||||
"total_recovered_submissions": result.total_recovered_submissions,
|
||||
"total_recovered_attachments": result.total_recovered_attachments,
|
||||
"total_recovered_notes": result.total_recovered_notes,
|
||||
"total_recovered_reviews": result.total_recovered_reviews,
|
||||
"items": [
|
||||
{
|
||||
"id": item.id,
|
||||
"name": item.name,
|
||||
"type": item.type,
|
||||
"success": item.success,
|
||||
"error": item.error,
|
||||
"recovered_counts": item.recovered_counts
|
||||
}
|
||||
for item in result.items
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user