252 lines
8.1 KiB
Python
Executable File
252 lines
8.1 KiB
Python
Executable File
"""
|
|
Raw Download Routes - API Mode
|
|
Handles NAS FileStation API downloads (với OTP authentication).
|
|
"""
|
|
|
|
from fastapi import APIRouter, HTTPException
|
|
from pydantic import BaseModel
|
|
from typing import List, Dict, Optional
|
|
import logging
|
|
|
|
from ..services import mongodb_service, nas_service, supabase_service, downloads_service
|
|
from ..common import get_download_destination_path
|
|
|
|
logger = logging.getLogger(__name__)
|
|
router = APIRouter(prefix="/api/raw-files", tags=["Raw API"])
|
|
|
|
|
|
# ==================== REQUEST MODELS ====================
|
|
|
|
class RawFileListRequest(BaseModel):
|
|
ge_id: str
|
|
lang: str
|
|
|
|
|
|
class OTPAuthRequest(BaseModel):
|
|
otp_code: str
|
|
|
|
|
|
class RawFileDownloadRequest(BaseModel):
|
|
files: List[Dict] # List of file objects with name, path, isdir
|
|
ge_id: str
|
|
lang: str
|
|
|
|
|
|
class RawFolderListRequest(BaseModel):
|
|
folder_path: str
|
|
ge_id: str # For context, though not strictly needed for folder listing
|
|
lang: str
|
|
|
|
|
|
# ==================== FILE LISTING ENDPOINTS ====================
|
|
|
|
@router.post('/list')
|
|
def list_raw_files(payload: RawFileListRequest):
|
|
"""
|
|
List raw files for a given GE ID and language.
|
|
Returns one of:
|
|
- success: List of files/folders
|
|
- otp_required: Session expired, OTP needed
|
|
- error: Various errors (no path found, NAS connection failed, etc.)
|
|
"""
|
|
try:
|
|
# Get path from MongoDB only (API mode)
|
|
nas_path = mongodb_service.get_path_from_tms_data(
|
|
payload.ge_id, payload.lang)
|
|
|
|
if not nas_path:
|
|
return {
|
|
"status": "error",
|
|
"message": f"Không tìm thấy đường dẫn cho GE ID {payload.ge_id} (Lang: {payload.lang})"
|
|
}
|
|
|
|
# Try to list files from NAS
|
|
status, files, message = nas_service.get_files_for_path(nas_path)
|
|
|
|
if status == "success":
|
|
return {
|
|
"status": "success",
|
|
"files": files,
|
|
"path": nas_path,
|
|
"message": message
|
|
}
|
|
elif status == "otp_required":
|
|
return {
|
|
"status": "otp_required",
|
|
"message": message
|
|
}
|
|
else: # error
|
|
# Append path to error message for context
|
|
enhanced_message = message
|
|
if message and "Thư mục không tồn tại" in message:
|
|
enhanced_message = f"{message}\n(Đường dẫn từ MongoDB: {nas_path})"
|
|
|
|
return {
|
|
"status": "error",
|
|
"message": enhanced_message
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"❌ [list_raw_files] Exception: {e}", exc_info=True)
|
|
raise HTTPException(status_code=500, detail=f"Lỗi hệ thống: {e}")
|
|
|
|
|
|
@router.post('/list-folder')
|
|
def list_folder_contents(payload: RawFolderListRequest):
|
|
"""
|
|
List contents of a specific folder in NAS.
|
|
Used for navigation when user double-clicks a folder.
|
|
"""
|
|
try:
|
|
status, files, message = nas_service.get_files_for_path(
|
|
payload.folder_path)
|
|
|
|
if status == "success":
|
|
return {
|
|
"status": "success",
|
|
"files": files,
|
|
"path": payload.folder_path,
|
|
"message": message
|
|
}
|
|
elif status == "otp_required":
|
|
return {
|
|
"status": "otp_required",
|
|
"message": message
|
|
}
|
|
else: # error
|
|
return {
|
|
"status": "error",
|
|
"message": message
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error in list_folder_contents: {e}")
|
|
raise HTTPException(status_code=500, detail=f"Lỗi hệ thống: {e}")
|
|
|
|
|
|
# ==================== AUTHENTICATION ENDPOINTS ====================
|
|
|
|
@router.post('/auth-otp')
|
|
def authenticate_otp(payload: OTPAuthRequest):
|
|
"""
|
|
Authenticate with OTP to establish NAS session.
|
|
Returns success or error status.
|
|
"""
|
|
try:
|
|
status, message = nas_service.authenticate_with_otp(payload.otp_code)
|
|
|
|
if status == "success":
|
|
return {
|
|
"status": "success",
|
|
"message": message
|
|
}
|
|
else: # error
|
|
return {
|
|
"status": "error",
|
|
"message": message
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error in authenticate_otp: {e}")
|
|
raise HTTPException(status_code=500, detail=f"Lỗi hệ thống: {e}")
|
|
|
|
|
|
# ==================== DOWNLOAD ENDPOINTS ====================
|
|
|
|
@router.post('/download')
|
|
def download_raw_files(payload: RawFileDownloadRequest):
|
|
"""
|
|
Create a download job in the queue.
|
|
Returns job_id immediately for client to poll status.
|
|
Background worker will process the actual download.
|
|
"""
|
|
try:
|
|
logger.debug(
|
|
f"Creating download job: {payload.ge_id} {payload.lang}, {len(payload.files)} files")
|
|
|
|
if not payload.files:
|
|
return {
|
|
"status": "error",
|
|
"message": "Không có file nào được chọn để tải xuống"
|
|
}
|
|
|
|
if not payload.ge_id or not payload.lang:
|
|
return {
|
|
"status": "error",
|
|
"message": "GE ID và Lang là bắt buộc"
|
|
}
|
|
|
|
# Calculate destination path using helper
|
|
destination_path = get_download_destination_path(
|
|
payload.ge_id, payload.lang)
|
|
|
|
# Get MongoDB path from first file
|
|
mongodb_path = None
|
|
if payload.files and len(payload.files) > 0:
|
|
first_file_path = payload.files[0].get('path', '')
|
|
if first_file_path:
|
|
# Extract parent directory from path (remove filename)
|
|
mongodb_path = '/'.join(first_file_path.split('/')
|
|
[:-1]) if '/' in first_file_path else first_file_path
|
|
|
|
# ✅ FIX: Use downloads_service (NEW) instead of supabase_service (OLD)
|
|
result = downloads_service.create_downloads_batch(
|
|
files=payload.files,
|
|
ge_id=payload.ge_id,
|
|
lang=payload.lang,
|
|
mode='api', # FileStation API mode
|
|
mongodb_path=mongodb_path,
|
|
destination_path=destination_path
|
|
)
|
|
|
|
if not result['success']:
|
|
return {
|
|
"status": "error",
|
|
"message": result.get('message', 'Không thể tạo batch downloads')
|
|
}
|
|
|
|
logger.debug(
|
|
f"Created downloads batch: {result['batch_id']} ({result['file_count']} files)")
|
|
|
|
return {
|
|
"status": "pending",
|
|
"message": "Batch đã được tạo và đang chờ xử lý",
|
|
"batch_id": result['batch_id'],
|
|
"download_ids": result['download_ids'],
|
|
"file_count": result['file_count'],
|
|
"mongodb_path": mongodb_path,
|
|
"destination_path": destination_path
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error creating download job: {e}")
|
|
raise HTTPException(status_code=500, detail=f"Lỗi hệ thống: {e}")
|
|
|
|
|
|
@router.get('/download-status/{batch_id}')
|
|
def get_download_status(batch_id: str):
|
|
"""
|
|
Get the status of a download batch.
|
|
Returns batch summary with all files' status.
|
|
"""
|
|
try:
|
|
summary = downloads_service.get_batch_summary(batch_id)
|
|
|
|
if not summary:
|
|
raise HTTPException(status_code=404, detail="Batch không tồn tại")
|
|
|
|
return {
|
|
"success": True,
|
|
"batch": summary
|
|
}
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
logger.error(f"Error getting job status: {e}")
|
|
raise HTTPException(status_code=500, detail=f"Lỗi hệ thống: {e}")
|
|
|
|
|
|
# ==================== OLD ENDPOINTS REMOVED ====================
|
|
# Download management endpoints đã được chuyển sang /api/downloads và /api/batches
|
|
# Xem downloads_routes.py để sử dụng API mới
|