When a reviewer saves the source language VTT during QC and confirms the re-translate dialog, all target languages are re-translated via Celery. Job transitions to `translating` and returns to `pending_qc` when done. Existing polling in useJob covers progress display. - schemas/job.py: add `retranslate_languages: bool` to VttUpdateRequest - audit_log.py: add VTT_RETRANSLATE audit action - translate_and_synthesize_task: accept languages/retranslate params, filter to specified languages, skip video render, return to PENDING_QC - routes_jobs.py: add _trigger_retranslation helper, call after VTT save - types/api.ts: add retranslate_languages to VttUpdateRequest - useJob.ts: invalidate all lang VTTs on retranslate - QCDetail.tsx: confirmation dialog when saving source VTT with targets Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
198 lines
5.6 KiB
Python
198 lines
5.6 KiB
Python
"""Audit log model for tracking sensitive operations."""
|
|
|
|
from datetime import datetime
|
|
from enum import Enum
|
|
from typing import Any
|
|
|
|
from bson import ObjectId
|
|
from pydantic import BaseModel, Field
|
|
|
|
from .user import PyObjectId
|
|
|
|
|
|
class AuditAction(str, Enum):
|
|
"""Enumeration of auditable actions."""
|
|
|
|
# Authentication actions
|
|
LOGIN_SUCCESS = "auth.login.success"
|
|
LOGIN_FAILURE = "auth.login.failure"
|
|
LOGOUT = "auth.logout"
|
|
TOKEN_REFRESH = "auth.token.refresh"
|
|
PASSWORD_CHANGE = "auth.password.change"
|
|
PASSWORD_RESET = "auth.password.reset"
|
|
|
|
# User management actions
|
|
USER_CREATE = "user.create"
|
|
USER_UPDATE = "user.update"
|
|
USER_DELETE = "user.delete"
|
|
USER_ROLE_CHANGE = "user.role.change"
|
|
USER_ACTIVATE = "user.activate"
|
|
USER_DEACTIVATE = "user.deactivate"
|
|
|
|
# Job management actions
|
|
JOB_CREATE = "job.create"
|
|
JOB_UPDATE = "job.update"
|
|
JOB_DELETE = "job.delete"
|
|
JOB_APPROVE = "job.approve"
|
|
JOB_REJECT = "job.reject"
|
|
JOB_CANCEL = "job.cancel"
|
|
JOB_STATUS_CHANGE = "job.status.change"
|
|
JOB_TASK_FAILED = "job.task.failed"
|
|
JOB_RETRY = "job.retry"
|
|
JOB_BULK_RETRY = "job.bulk_retry"
|
|
|
|
# File operations
|
|
FILE_UPLOAD = "file.upload"
|
|
FILE_DOWNLOAD = "file.download"
|
|
FILE_DELETE = "file.delete"
|
|
FILE_ACCESS = "file.access"
|
|
|
|
# VTT editing actions
|
|
VTT_EDIT = "vtt.edit"
|
|
VTT_APPROVE = "vtt.approve"
|
|
VTT_REJECT = "vtt.reject"
|
|
VTT_RETRANSLATE = "vtt.retranslate"
|
|
|
|
# Per-language QC actions
|
|
LANGUAGE_QC_ASSIGN = "language_qc.assign"
|
|
LANGUAGE_QC_REASSIGN = "language_qc.reassign"
|
|
LANGUAGE_QC_REVIEWER_ASSIGN = "language_qc.reviewer_assign"
|
|
LANGUAGE_QC_REVIEWER_REASSIGN = "language_qc.reviewer_reassign"
|
|
LANGUAGE_QC_SUBMIT = "language_qc.submit"
|
|
LANGUAGE_QC_OPEN_REVIEW = "language_qc.open_review"
|
|
LANGUAGE_QC_APPROVE = "language_qc.approve"
|
|
LANGUAGE_QC_REJECT = "language_qc.reject"
|
|
LANGUAGE_QC_REOPEN = "language_qc.reopen"
|
|
LANGUAGE_QC_COMMENT = "language_qc.comment"
|
|
|
|
# Admin actions
|
|
ADMIN_CONFIG_CHANGE = "admin.config.change"
|
|
ADMIN_SYSTEM_ACTION = "admin.system.action"
|
|
ADMIN_DATA_EXPORT = "admin.data.export"
|
|
ADMIN_AUDIT_ACCESS = "admin.audit.access"
|
|
|
|
# Glossary management
|
|
GLOSSARY_UPLOAD = "glossary.upload"
|
|
GLOSSARY_VERSION_UPLOAD = "glossary.version.upload"
|
|
GLOSSARY_ACTIVATE = "glossary.activate"
|
|
GLOSSARY_ARCHIVE = "glossary.archive"
|
|
|
|
# Security events
|
|
RATE_LIMIT_EXCEEDED = "security.rate_limit.exceeded"
|
|
VALIDATION_FAILURE = "security.validation.failure"
|
|
UNAUTHORIZED_ACCESS = "security.unauthorized.access"
|
|
SUSPICIOUS_ACTIVITY = "security.suspicious.activity"
|
|
|
|
|
|
class AuditLogSeverity(str, Enum):
|
|
"""Severity levels for audit events."""
|
|
|
|
INFO = "info" # Normal operations
|
|
WARNING = "warning" # Suspicious but not critical
|
|
ERROR = "error" # Failed operations
|
|
CRITICAL = "critical" # Security incidents
|
|
|
|
|
|
class AuditLog(BaseModel):
|
|
"""Audit log entry model."""
|
|
|
|
id: PyObjectId | None = Field(default_factory=lambda: str(ObjectId()), alias="_id")
|
|
|
|
# Core audit fields
|
|
timestamp: datetime = Field(default_factory=datetime.utcnow)
|
|
action: AuditAction
|
|
severity: AuditLogSeverity = AuditLogSeverity.INFO
|
|
|
|
# Actor information
|
|
user_id: PyObjectId | None = None
|
|
user_email: str | None = None
|
|
user_role: str | None = None
|
|
|
|
# Request context
|
|
ip_address: str | None = None
|
|
user_agent: str | None = None
|
|
request_id: str | None = None
|
|
session_id: str | None = None
|
|
|
|
# Resource information
|
|
resource_type: str | None = None # e.g., "job", "user", "file"
|
|
resource_id: str | None = None
|
|
resource_name: str | None = None
|
|
|
|
# Action details
|
|
description: str
|
|
details: dict[str, Any] = Field(default_factory=dict)
|
|
|
|
# Outcome
|
|
success: bool = True
|
|
error_message: str | None = None
|
|
|
|
# Additional metadata
|
|
environment: str = "prod"
|
|
service_name: str = "accessible-video-api"
|
|
api_version: str = "v1"
|
|
|
|
class Config:
|
|
populate_by_name = True
|
|
arbitrary_types_allowed = True
|
|
json_encoders = {ObjectId: str}
|
|
|
|
|
|
class AuditLogCreate(BaseModel):
|
|
"""Schema for creating audit log entries."""
|
|
|
|
action: AuditAction
|
|
severity: AuditLogSeverity = AuditLogSeverity.INFO
|
|
description: str
|
|
|
|
# Optional fields that can be provided
|
|
user_id: PyObjectId | None = None
|
|
user_email: str | None = None
|
|
user_role: str | None = None
|
|
ip_address: str | None = None
|
|
user_agent: str | None = None
|
|
request_id: str | None = None
|
|
resource_type: str | None = None
|
|
resource_id: str | None = None
|
|
resource_name: str | None = None
|
|
details: dict[str, Any] = Field(default_factory=dict)
|
|
success: bool = True
|
|
error_message: str | None = None
|
|
|
|
|
|
class AuditLogQuery(BaseModel):
|
|
"""Schema for querying audit logs."""
|
|
|
|
# Time range
|
|
start_date: datetime | None = None
|
|
end_date: datetime | None = None
|
|
|
|
# Filters
|
|
action: AuditAction | None = None
|
|
severity: AuditLogSeverity | None = None
|
|
user_id: PyObjectId | None = None
|
|
user_email: str | None = None
|
|
resource_type: str | None = None
|
|
resource_id: str | None = None
|
|
success: bool | None = None
|
|
|
|
# Search
|
|
search: str | None = None # Full-text search in description and details
|
|
|
|
# Pagination
|
|
skip: int = 0
|
|
limit: int = 100
|
|
|
|
# Sorting
|
|
sort_by: str = "timestamp"
|
|
sort_order: int = -1 # -1 for descending, 1 for ascending
|
|
|
|
|
|
class AuditLogResponse(BaseModel):
|
|
"""Response schema for audit log queries."""
|
|
|
|
logs: list[AuditLog]
|
|
total_count: int
|
|
page: int
|
|
page_size: int
|
|
has_more: bool
|