diff --git a/backend/app/api/v1/routes_admin.py b/backend/app/api/v1/routes_admin.py
index 4bb77ec..f7bc80a 100644
--- a/backend/app/api/v1/routes_admin.py
+++ b/backend/app/api/v1/routes_admin.py
@@ -278,10 +278,10 @@ async def admin_reset_password(
@router.get("/stats", response_model=AdminStatsResponse)
async def get_admin_stats(
- current_user: User = Depends(require_roles(UserRole.ADMIN)),
+ current_user: User = Depends(require_roles(UserRole.PRODUCTION, UserRole.ADMIN)),
db: AsyncIOMotorDatabase = Depends(get_database),
):
- """Get system statistics (admin only)"""
+ """Get system statistics (production/admin only)"""
# Get user count
total_users = await db.users.count_documents({"is_active": True})
@@ -336,10 +336,10 @@ async def get_admin_stats(
@router.get("/health/detailed")
async def detailed_health_check(
- current_user: User = Depends(require_roles(UserRole.ADMIN, UserRole.REVIEWER)),
+ current_user: User = Depends(require_roles(UserRole.REVIEWER, UserRole.PRODUCTION, UserRole.ADMIN)),
db: AsyncIOMotorDatabase = Depends(get_database),
):
- """Detailed health check with system component status (admin/reviewer only)"""
+ """Detailed health check with system component status (reviewer/production/admin only)"""
health_status = {
"status": "healthy",
"timestamp": datetime.utcnow().isoformat(),
@@ -405,10 +405,10 @@ async def detailed_health_check(
@router.get("/jobs/stats")
async def get_job_statistics(
days: int = Query(7, ge=1, le=90),
- current_user: User = Depends(require_roles(UserRole.ADMIN, UserRole.REVIEWER)),
+ current_user: User = Depends(require_roles(UserRole.REVIEWER, UserRole.PRODUCTION, UserRole.ADMIN)),
db: AsyncIOMotorDatabase = Depends(get_database),
):
- """Get job processing statistics (admin/reviewer only)"""
+ """Get job processing statistics (reviewer/production/admin only)"""
since_date = datetime.utcnow() - timedelta(days=days)
# Jobs created in period
@@ -534,10 +534,10 @@ async def get_audit_logs(
days: int = Query(7, ge=1, le=90),
page: int = Query(1, ge=1),
size: int = Query(50, ge=1, le=200),
- current_user: User = Depends(require_roles(UserRole.ADMIN)),
+ current_user: User = Depends(require_roles(UserRole.PRODUCTION, UserRole.ADMIN)),
db: AsyncIOMotorDatabase = Depends(get_database),
):
- """Get audit logs with filtering (admin only)"""
+ """Get audit logs with filtering (production/admin only)"""
query = {
"when": {"$gte": datetime.utcnow() - timedelta(days=days)}
}
@@ -572,10 +572,10 @@ async def get_audit_logs(
@router.post("/maintenance/reprocess-job/{job_id}")
async def reprocess_job(
job_id: str,
- current_user: User = Depends(require_roles(UserRole.ADMIN)),
+ current_user: User = Depends(require_roles(UserRole.PRODUCTION, UserRole.ADMIN)),
db: AsyncIOMotorDatabase = Depends(get_database),
):
- """Force reprocessing of a job (admin emergency function)"""
+ """Force reprocessing of a job (production/admin emergency function)"""
# Check if job exists
job_doc = await db.jobs.find_one({"_id": job_id})
if not job_doc:
@@ -626,11 +626,11 @@ async def reprocess_job(
@router.get("/audit-logs", response_model=AuditLogResponse)
-async def get_audit_logs(
+async def get_audit_logs_detailed(
# Time range
start_date: Optional[datetime] = Query(None, description="Start date for audit logs"),
end_date: Optional[datetime] = Query(None, description="End date for audit logs"),
-
+
# Filters
action: Optional[str] = Query(None, description="Filter by action type"),
severity: Optional[str] = Query(None, description="Filter by severity level"),
@@ -638,22 +638,22 @@ async def get_audit_logs(
resource_type: Optional[str] = Query(None, description="Filter by resource type"),
resource_id: Optional[str] = Query(None, description="Filter by resource ID"),
success: Optional[bool] = Query(None, description="Filter by success status"),
-
+
# Search
search: Optional[str] = Query(None, description="Search in description and details"),
-
+
# Pagination
page: int = Query(1, ge=1, description="Page number"),
size: int = Query(50, ge=1, le=500, description="Page size"),
-
+
# Sorting
sort_by: str = Query("timestamp", description="Field to sort by"),
sort_order: int = Query(-1, ge=-1, le=1, description="Sort order (-1 desc, 1 asc)"),
-
- current_user: User = Depends(require_roles(UserRole.ADMIN)),
+
+ current_user: User = Depends(require_roles(UserRole.PRODUCTION, UserRole.ADMIN)),
request: Request = None,
):
- """Get audit logs with filtering and pagination (admin only)"""
+ """Get audit logs with filtering and pagination (production/admin only)"""
# Log audit log access
await audit_logger.log_action(
@@ -698,10 +698,10 @@ async def get_audit_logs(
async def get_user_audit_logs(
user_id: str,
days: int = Query(30, ge=1, le=365, description="Number of days to look back"),
- current_user: User = Depends(require_roles(UserRole.ADMIN)),
+ current_user: User = Depends(require_roles(UserRole.PRODUCTION, UserRole.ADMIN)),
request: Request = None,
):
- """Get audit logs for a specific user (admin only)"""
+ """Get audit logs for a specific user (production/admin only)"""
# Validate user_id
try:
@@ -730,10 +730,10 @@ async def get_user_audit_logs(
@router.get("/audit-logs/security")
async def get_security_events(
hours: int = Query(24, ge=1, le=168, description="Number of hours to look back"),
- current_user: User = Depends(require_roles(UserRole.ADMIN)),
+ current_user: User = Depends(require_roles(UserRole.PRODUCTION, UserRole.ADMIN)),
request: Request = None,
):
- """Get recent security events (admin only)"""
+ """Get recent security events (production/admin only)"""
# Log access to security events
await audit_logger.log_action(
diff --git a/backend/app/api/v1/routes_auth.py b/backend/app/api/v1/routes_auth.py
index 5ee77b9..a71e9dc 100644
--- a/backend/app/api/v1/routes_auth.py
+++ b/backend/app/api/v1/routes_auth.py
@@ -166,7 +166,7 @@ async def microsoft_login(
"email": user_info.email,
"full_name": user_info.name,
"hashed_password": None, # No password for Microsoft users
- "role": UserRole.CLIENT.value,
+ "role": UserRole.PRODUCTION.value,
"auth_provider": AuthProvider.MICROSOFT.value,
"is_active": True,
"created_at": datetime.utcnow(),
diff --git a/backend/app/api/v1/routes_jobs.py b/backend/app/api/v1/routes_jobs.py
index 14b0766..4d82af9 100644
--- a/backend/app/api/v1/routes_jobs.py
+++ b/backend/app/api/v1/routes_jobs.py
@@ -161,10 +161,10 @@ async def create_job(
@router.delete("/bulk", response_model=BulkDeleteResponse)
async def bulk_delete_jobs(
request: BulkDeleteRequest,
- current_user: User = Depends(require_roles(UserRole.ADMIN)),
+ current_user: User = Depends(require_roles(UserRole.PRODUCTION, UserRole.ADMIN)),
db: AsyncIOMotorDatabase = Depends(get_database),
):
- """Bulk delete jobs (admin only)"""
+ """Bulk delete jobs (production/admin only)"""
job_ids = request.job_ids
logger.info(f"Bulk deleting {len(job_ids)} jobs requested by {current_user.email}")
@@ -339,7 +339,7 @@ async def get_job(
async def approve_english(
job_id: str,
request: ApproveEnglishRequest,
- current_user: User = Depends(require_roles(UserRole.REVIEWER, UserRole.ADMIN)),
+ current_user: User = Depends(require_roles(UserRole.REVIEWER, UserRole.PRODUCTION, UserRole.ADMIN)),
db: AsyncIOMotorDatabase = Depends(get_database),
):
result = await db.jobs.find_one_and_update(
@@ -394,7 +394,7 @@ async def approve_english(
async def reject_job(
job_id: str,
request: RejectJobRequest,
- current_user: User = Depends(require_roles(UserRole.REVIEWER, UserRole.ADMIN)),
+ current_user: User = Depends(require_roles(UserRole.REVIEWER, UserRole.PRODUCTION, UserRole.ADMIN)),
db: AsyncIOMotorDatabase = Depends(get_database),
):
result = await db.jobs.find_one_and_update(
@@ -441,7 +441,7 @@ async def reject_job(
async def complete_job(
job_id: str,
request: CompleteJobRequest,
- current_user: User = Depends(require_roles(UserRole.REVIEWER, UserRole.ADMIN)),
+ current_user: User = Depends(require_roles(UserRole.REVIEWER, UserRole.PRODUCTION, UserRole.ADMIN)),
db: AsyncIOMotorDatabase = Depends(get_database),
):
# Get job for validation
@@ -518,7 +518,7 @@ async def complete_job(
async def reject_final_review(
job_id: str,
request: RejectJobRequest,
- current_user: User = Depends(require_roles(UserRole.REVIEWER, UserRole.ADMIN)),
+ current_user: User = Depends(require_roles(UserRole.REVIEWER, UserRole.PRODUCTION, UserRole.ADMIN)),
db: AsyncIOMotorDatabase = Depends(get_database),
):
result = await db.jobs.find_one_and_update(
@@ -706,7 +706,7 @@ async def get_job_vtt_content(
async def update_job_vtt_content(
job_id: str,
request: VttUpdateRequest,
- current_user: User = Depends(require_roles(UserRole.REVIEWER, UserRole.ADMIN)),
+ current_user: User = Depends(require_roles(UserRole.REVIEWER, UserRole.PRODUCTION, UserRole.ADMIN)),
db: AsyncIOMotorDatabase = Depends(get_database),
):
"""Update VTT content for a job"""
@@ -800,7 +800,7 @@ async def update_job_vtt_content(
async def adjust_vtt_timing(
job_id: str,
request: VttTimingAdjustRequest,
- current_user: User = Depends(require_roles(UserRole.REVIEWER, UserRole.ADMIN)),
+ current_user: User = Depends(require_roles(UserRole.REVIEWER, UserRole.PRODUCTION, UserRole.ADMIN)),
db: AsyncIOMotorDatabase = Depends(get_database),
):
"""Adjust timing of VTT content by a specified offset"""
@@ -1041,7 +1041,7 @@ async def _delete_job_gcs_assets(job_id: str, job_doc: dict):
@router.get("/{job_id}/validate", response_model=AssetValidationResponse)
async def validate_job_assets(
job_id: str,
- current_user: User = Depends(require_roles(UserRole.REVIEWER, UserRole.ADMIN)),
+ current_user: User = Depends(require_roles(UserRole.REVIEWER, UserRole.PRODUCTION, UserRole.ADMIN)),
db: AsyncIOMotorDatabase = Depends(get_database),
):
"""Validate job assets before completion"""
diff --git a/backend/app/models/user.py b/backend/app/models/user.py
index 22a8cbc..7899343 100644
--- a/backend/app/models/user.py
+++ b/backend/app/models/user.py
@@ -21,6 +21,7 @@ PyObjectId = Annotated[str, BeforeValidator(validate_object_id)]
class UserRole(str, Enum):
CLIENT = "client"
REVIEWER = "reviewer"
+ PRODUCTION = "production"
ADMIN = "admin"
diff --git a/backend/create_test_users.py b/backend/create_test_users.py
index 70c86cb..4d5d237 100644
--- a/backend/create_test_users.py
+++ b/backend/create_test_users.py
@@ -34,6 +34,7 @@ async def create_test_users():
"hashed_password": pwd_context.hash("admin"),
"full_name": "Admin User",
"role": UserRole.ADMIN.value,
+ "auth_provider": "local",
"is_active": True,
"created_at": datetime.utcnow(),
"updated_at": datetime.utcnow(),
@@ -44,6 +45,18 @@ async def create_test_users():
"hashed_password": pwd_context.hash("reviewer"),
"full_name": "Reviewer User",
"role": UserRole.REVIEWER.value,
+ "auth_provider": "local",
+ "is_active": True,
+ "created_at": datetime.utcnow(),
+ "updated_at": datetime.utcnow(),
+ },
+ {
+ "_id": "production-001",
+ "email": "production@example.com",
+ "hashed_password": pwd_context.hash("production"),
+ "full_name": "Production User",
+ "role": UserRole.PRODUCTION.value,
+ "auth_provider": "local",
"is_active": True,
"created_at": datetime.utcnow(),
"updated_at": datetime.utcnow(),
@@ -54,6 +67,7 @@ async def create_test_users():
"hashed_password": pwd_context.hash("client123"),
"full_name": "Client User",
"role": UserRole.CLIENT.value,
+ "auth_provider": "local",
"is_active": True,
"created_at": datetime.utcnow(),
"updated_at": datetime.utcnow(),
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index a45add2..f1abd73 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -67,28 +67,28 @@ function AppContent() {
} />
Admin: admin@example.com / admin
+Production: production@example.com / production
Reviewer: reviewer@example.com / reviewer
diff --git a/frontend/src/routes/admin/UserDetail.tsx b/frontend/src/routes/admin/UserDetail.tsx index e14ddb2..5fc95af 100644 --- a/frontend/src/routes/admin/UserDetail.tsx +++ b/frontend/src/routes/admin/UserDetail.tsx @@ -164,6 +164,7 @@ export function UserDetail() { > + diff --git a/frontend/src/routes/admin/UserList.tsx b/frontend/src/routes/admin/UserList.tsx index 4956030..183a96d 100644 --- a/frontend/src/routes/admin/UserList.tsx +++ b/frontend/src/routes/admin/UserList.tsx @@ -118,6 +118,7 @@ export function UserList() { > + @@ -201,6 +202,7 @@ export function UserList() {