607 lines
19 KiB
Python
607 lines
19 KiB
Python
|
|
"""
|
||
|
|
Raw Download Routes - Sharing Link Mode
|
||
|
|
Handles Synology sharing link downloads (với Selenium + OTP).
|
||
|
|
"""
|
||
|
|
|
||
|
|
from fastapi import APIRouter, HTTPException, Request, Response
|
||
|
|
from pydantic import BaseModel
|
||
|
|
from typing import List, Dict, Optional
|
||
|
|
import logging
|
||
|
|
import os
|
||
|
|
import uuid
|
||
|
|
import time
|
||
|
|
from collections import defaultdict
|
||
|
|
|
||
|
|
from ..services import mongodb_service, nas_service, nas_sharing_service, supabase_service, downloads_service
|
||
|
|
from ..common import get_download_destination_path
|
||
|
|
|
||
|
|
logger = logging.getLogger(__name__)
|
||
|
|
router = APIRouter(prefix="/api/sharing-link", tags=["Raw Sharing"])
|
||
|
|
|
||
|
|
# ==================== RATE LIMITING ====================
|
||
|
|
# Track last request time per sharing_id to prevent NAS API rate limit errors
|
||
|
|
_last_request_time: Dict[str, float] = defaultdict(float)
|
||
|
|
_rate_limit_window_ms = 200 # Minimum 200ms between requests for same sharing_id
|
||
|
|
|
||
|
|
|
||
|
|
# ==================== REQUEST MODELS ====================
|
||
|
|
|
||
|
|
class SharingLinkRequest(BaseModel):
|
||
|
|
url: str
|
||
|
|
|
||
|
|
|
||
|
|
class SharingLinkDownloadRequest(BaseModel):
|
||
|
|
sharing_id: str
|
||
|
|
files: List[Dict] # List of file objects with name, path, isdir
|
||
|
|
ge_id: Optional[str] = None # Optional: for organizing files by project
|
||
|
|
lang: Optional[str] = None # Optional: for organizing files by project
|
||
|
|
|
||
|
|
|
||
|
|
class SharingLinkFromDbRequest(BaseModel):
|
||
|
|
ge_id: str
|
||
|
|
lang: str
|
||
|
|
|
||
|
|
|
||
|
|
class SharingOtpSubmit(BaseModel):
|
||
|
|
otp_code: str
|
||
|
|
|
||
|
|
|
||
|
|
# ==================== SHARING LINK PROCESSING ====================
|
||
|
|
|
||
|
|
@router.post('/get-from-db')
|
||
|
|
def get_sharing_link_from_db(payload: SharingLinkFromDbRequest):
|
||
|
|
"""
|
||
|
|
Query MongoDB titles_data collection to get linkRaw field.
|
||
|
|
Returns sharing link from database or throws error with record details.
|
||
|
|
"""
|
||
|
|
try:
|
||
|
|
# Query MongoDB
|
||
|
|
collection = mongodb_service.get_titles_collection()
|
||
|
|
query = {
|
||
|
|
"geId": str(payload.ge_id).strip(),
|
||
|
|
"lang": str(payload.lang).strip().upper()
|
||
|
|
}
|
||
|
|
|
||
|
|
# Find all matching records
|
||
|
|
documents = list(collection.find(query))
|
||
|
|
|
||
|
|
# Validation 1: No records found
|
||
|
|
if len(documents) == 0:
|
||
|
|
raise HTTPException(
|
||
|
|
status_code=404,
|
||
|
|
detail={
|
||
|
|
"error": "Không tìm thấy record",
|
||
|
|
"query": {"geId": payload.ge_id, "lang": payload.lang.upper()}
|
||
|
|
}
|
||
|
|
)
|
||
|
|
|
||
|
|
# Validation 2: Multiple records (should not happen with unique constraint)
|
||
|
|
if len(documents) > 1:
|
||
|
|
record_info = [
|
||
|
|
{
|
||
|
|
"geId": doc.get("geId"),
|
||
|
|
"lang": doc.get("lang"),
|
||
|
|
"linkRaw": doc.get("linkRaw"),
|
||
|
|
"path": doc.get("path")
|
||
|
|
}
|
||
|
|
for doc in documents
|
||
|
|
]
|
||
|
|
raise HTTPException(
|
||
|
|
status_code=400,
|
||
|
|
detail={
|
||
|
|
"error": "Tìm thấy nhiều hơn 1 record",
|
||
|
|
"records": record_info
|
||
|
|
}
|
||
|
|
)
|
||
|
|
|
||
|
|
# Get single record
|
||
|
|
document = documents[0]
|
||
|
|
link_raw = document.get("linkRaw")
|
||
|
|
|
||
|
|
# Validation 3: linkRaw is empty or null
|
||
|
|
if not link_raw or not isinstance(link_raw, str) or link_raw.strip() == "":
|
||
|
|
record_info = {
|
||
|
|
"geId": document.get("geId"),
|
||
|
|
"lang": document.get("lang"),
|
||
|
|
"linkRaw": link_raw,
|
||
|
|
"path": document.get("path")
|
||
|
|
}
|
||
|
|
raise HTTPException(
|
||
|
|
status_code=400,
|
||
|
|
detail={
|
||
|
|
"error": "Trường linkRaw trống hoặc null",
|
||
|
|
"record": record_info
|
||
|
|
}
|
||
|
|
)
|
||
|
|
|
||
|
|
# Validation 4: linkRaw is not a valid link
|
||
|
|
link_raw_stripped = link_raw.strip()
|
||
|
|
if not (link_raw_stripped.startswith("http://") or link_raw_stripped.startswith("https://")):
|
||
|
|
record_info = {
|
||
|
|
"geId": document.get("geId"),
|
||
|
|
"lang": document.get("lang"),
|
||
|
|
"linkRaw": link_raw,
|
||
|
|
"path": document.get("path")
|
||
|
|
}
|
||
|
|
raise HTTPException(
|
||
|
|
status_code=400,
|
||
|
|
detail={
|
||
|
|
"error": "linkRaw không phải là liên kết hợp lệ (phải bắt đầu bằng http:// hoặc https://)",
|
||
|
|
"record": record_info
|
||
|
|
}
|
||
|
|
)
|
||
|
|
|
||
|
|
# Success - return sharing link
|
||
|
|
return {
|
||
|
|
"success": True,
|
||
|
|
"sharing_link": link_raw_stripped,
|
||
|
|
"record": {
|
||
|
|
"geId": document.get("geId"),
|
||
|
|
"lang": document.get("lang"),
|
||
|
|
"path": document.get("path")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
except HTTPException:
|
||
|
|
raise
|
||
|
|
except Exception as e:
|
||
|
|
logger.error(f"Error querying MongoDB for sharing link: {e}")
|
||
|
|
raise HTTPException(status_code=500, detail=f"Lỗi hệ thống: {e}")
|
||
|
|
|
||
|
|
|
||
|
|
@router.post('/process')
|
||
|
|
def process_sharing_link(payload: SharingLinkRequest):
|
||
|
|
"""
|
||
|
|
Process sharing link to extract file list.
|
||
|
|
Returns: request_id for polling result
|
||
|
|
"""
|
||
|
|
try:
|
||
|
|
# Validate URL format
|
||
|
|
if not payload.url or 'sharing' not in payload.url:
|
||
|
|
raise HTTPException(status_code=400, detail="URL không hợp lệ")
|
||
|
|
|
||
|
|
# Submit to worker queue
|
||
|
|
result = nas_sharing_service.process_sharing_link(payload.url)
|
||
|
|
|
||
|
|
return {
|
||
|
|
"success": True,
|
||
|
|
"request_id": result['request_id'],
|
||
|
|
"status": result['status'],
|
||
|
|
"message": "Đang xử lý sharing link..."
|
||
|
|
}
|
||
|
|
|
||
|
|
except Exception as e:
|
||
|
|
logger.error(f"Error processing sharing link: {e}")
|
||
|
|
raise HTTPException(status_code=500, detail=f"Lỗi hệ thống: {e}")
|
||
|
|
|
||
|
|
|
||
|
|
@router.get('/related-projects')
|
||
|
|
def get_related_projects(link_raw: str):
|
||
|
|
"""
|
||
|
|
Tìm tất cả GE projects có cùng sharing link (linkRaw).
|
||
|
|
|
||
|
|
Query params:
|
||
|
|
link_raw: Sharing link URL
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
{
|
||
|
|
"success": true,
|
||
|
|
"projects": [
|
||
|
|
{"ge_id": "1000", "lang": "DE"},
|
||
|
|
{"ge_id": "2000", "lang": "KO"}
|
||
|
|
],
|
||
|
|
"total": 2
|
||
|
|
}
|
||
|
|
"""
|
||
|
|
try:
|
||
|
|
if not link_raw or not link_raw.strip():
|
||
|
|
raise HTTPException(
|
||
|
|
status_code=400, detail="link_raw không được rỗng")
|
||
|
|
|
||
|
|
link_raw_normalized = link_raw.strip()
|
||
|
|
|
||
|
|
# Query MongoDB titles_data - tìm tất cả records có cùng linkRaw
|
||
|
|
collection = mongodb_service.get_titles_collection()
|
||
|
|
documents = list(collection.find(
|
||
|
|
{"linkRaw": link_raw_normalized},
|
||
|
|
{"geId": 1, "lang": 1, "_id": 0} # Chỉ lấy geId và lang
|
||
|
|
))
|
||
|
|
|
||
|
|
# Format kết quả
|
||
|
|
projects = [
|
||
|
|
{
|
||
|
|
"ge_id": doc.get("geId"),
|
||
|
|
"lang": doc.get("lang")
|
||
|
|
}
|
||
|
|
for doc in documents
|
||
|
|
]
|
||
|
|
|
||
|
|
logger.debug(
|
||
|
|
f"Found {len(projects)} projects with linkRaw: {link_raw_normalized}")
|
||
|
|
|
||
|
|
return {
|
||
|
|
"success": True,
|
||
|
|
"projects": projects,
|
||
|
|
"total": len(projects)
|
||
|
|
}
|
||
|
|
|
||
|
|
except HTTPException:
|
||
|
|
raise
|
||
|
|
except Exception as e:
|
||
|
|
logger.error(f"Error querying related projects: {e}")
|
||
|
|
raise HTTPException(status_code=500, detail=f"Lỗi hệ thống: {e}")
|
||
|
|
|
||
|
|
|
||
|
|
@router.get('/related-projects-by-ge')
|
||
|
|
def get_related_projects_by_ge(ge_id: str, lang: str):
|
||
|
|
"""
|
||
|
|
Tìm tất cả GE projects có cùng sharing link dựa trên ge_id và lang.
|
||
|
|
|
||
|
|
Query params:
|
||
|
|
ge_id: GE ID
|
||
|
|
lang: Language code
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
{
|
||
|
|
"success": true,
|
||
|
|
"projects": [
|
||
|
|
{"ge_id": "1000", "lang": "DE"},
|
||
|
|
{"ge_id": "2000", "lang": "KO"}
|
||
|
|
],
|
||
|
|
"total": 2
|
||
|
|
}
|
||
|
|
"""
|
||
|
|
try:
|
||
|
|
if not ge_id or not ge_id.strip():
|
||
|
|
raise HTTPException(
|
||
|
|
status_code=400, detail="ge_id không được rỗng")
|
||
|
|
if not lang or not lang.strip():
|
||
|
|
raise HTTPException(status_code=400, detail="lang không được rỗng")
|
||
|
|
|
||
|
|
# Step 1: Get linkRaw from titles_data
|
||
|
|
link_raw = mongodb_service.get_sharing_link_from_tms_data(ge_id, lang)
|
||
|
|
if not link_raw:
|
||
|
|
return {
|
||
|
|
"success": True,
|
||
|
|
"projects": [],
|
||
|
|
"total": 0
|
||
|
|
}
|
||
|
|
|
||
|
|
# Step 2: Find all projects with same linkRaw
|
||
|
|
collection = mongodb_service.get_titles_collection()
|
||
|
|
documents = list(collection.find(
|
||
|
|
{"linkRaw": link_raw},
|
||
|
|
{"geId": 1, "lang": 1, "_id": 0}
|
||
|
|
))
|
||
|
|
|
||
|
|
# Format kết quả
|
||
|
|
projects = [
|
||
|
|
{
|
||
|
|
"ge_id": doc.get("geId"),
|
||
|
|
"lang": doc.get("lang")
|
||
|
|
}
|
||
|
|
for doc in documents
|
||
|
|
]
|
||
|
|
|
||
|
|
logger.debug(
|
||
|
|
f"Found {len(projects)} related projects for GE {ge_id} {lang}")
|
||
|
|
|
||
|
|
return {
|
||
|
|
"success": True,
|
||
|
|
"projects": projects,
|
||
|
|
"total": len(projects)
|
||
|
|
}
|
||
|
|
|
||
|
|
except HTTPException:
|
||
|
|
raise
|
||
|
|
except Exception as e:
|
||
|
|
logger.error(f"Error querying related projects by GE: {e}")
|
||
|
|
raise HTTPException(status_code=500, detail=f"Lỗi hệ thống: {e}")
|
||
|
|
|
||
|
|
|
||
|
|
@router.get('/project-note')
|
||
|
|
def get_project_note(ge_id: str, lang: str):
|
||
|
|
"""
|
||
|
|
Lấy note từ collection titlelist_data cho GE project cụ thể.
|
||
|
|
|
||
|
|
Query params:
|
||
|
|
ge_id: GE ID
|
||
|
|
lang: Language code
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
{
|
||
|
|
"success": true,
|
||
|
|
"note": "Content of note field"
|
||
|
|
}
|
||
|
|
"""
|
||
|
|
try:
|
||
|
|
if not ge_id or not ge_id.strip():
|
||
|
|
raise HTTPException(
|
||
|
|
status_code=400, detail="ge_id không được rỗng")
|
||
|
|
if not lang or not lang.strip():
|
||
|
|
raise HTTPException(status_code=400, detail="lang không được rỗng")
|
||
|
|
|
||
|
|
# Query MongoDB titlelist_data collection
|
||
|
|
db = mongodb_service.get_db_connection()
|
||
|
|
collection = db['titlelist_data']
|
||
|
|
|
||
|
|
document = collection.find_one(
|
||
|
|
{
|
||
|
|
"geId": str(ge_id).strip(),
|
||
|
|
"lang": str(lang).strip().upper()
|
||
|
|
},
|
||
|
|
{"note": 1, "_id": 0}
|
||
|
|
)
|
||
|
|
|
||
|
|
if not document:
|
||
|
|
return {
|
||
|
|
"success": True,
|
||
|
|
"note": None
|
||
|
|
}
|
||
|
|
|
||
|
|
note_content = document.get("note")
|
||
|
|
|
||
|
|
logger.debug(f"Found note for {ge_id} {lang}: {bool(note_content)}")
|
||
|
|
|
||
|
|
return {
|
||
|
|
"success": True,
|
||
|
|
"note": note_content if note_content else None
|
||
|
|
}
|
||
|
|
|
||
|
|
except HTTPException:
|
||
|
|
raise
|
||
|
|
except Exception as e:
|
||
|
|
logger.error(f"Error querying project note: {e}")
|
||
|
|
raise HTTPException(status_code=500, detail=f"Lỗi hệ thống: {e}")
|
||
|
|
|
||
|
|
|
||
|
|
@router.get('/result/{request_id}')
|
||
|
|
def get_sharing_result(request_id: str):
|
||
|
|
"""
|
||
|
|
Poll result of sharing link processing.
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
- status: "pending" | "success" | "error"
|
||
|
|
- If success: sharing_id, path, files, total_files
|
||
|
|
- If error: message
|
||
|
|
"""
|
||
|
|
try:
|
||
|
|
result = nas_sharing_service.get_sharing_result(request_id)
|
||
|
|
|
||
|
|
if not result:
|
||
|
|
raise HTTPException(
|
||
|
|
status_code=404, detail="Request không tồn tại")
|
||
|
|
|
||
|
|
return result
|
||
|
|
|
||
|
|
except HTTPException:
|
||
|
|
raise
|
||
|
|
except Exception as e:
|
||
|
|
logger.error(f"Error getting sharing result: {e}")
|
||
|
|
raise HTTPException(status_code=500, detail=f"Lỗi hệ thống: {e}")
|
||
|
|
|
||
|
|
|
||
|
|
# ==================== FOLDER NAVIGATION ====================
|
||
|
|
|
||
|
|
@router.post('/list-folder')
|
||
|
|
def list_sharing_folder(payload: dict):
|
||
|
|
"""
|
||
|
|
List contents of a subfolder in sharing link.
|
||
|
|
Used when user double-clicks a folder in sharing link file list.
|
||
|
|
|
||
|
|
Payload:
|
||
|
|
- sharing_id: Sharing ID from initial process
|
||
|
|
- folder_path: Path to folder to list (e.g., "/subfolder_name")
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
- status: "success" | "error"
|
||
|
|
- files: List of files/folders
|
||
|
|
- path: Current folder path
|
||
|
|
"""
|
||
|
|
try:
|
||
|
|
sharing_id = payload.get('sharing_id')
|
||
|
|
folder_path = payload.get('folder_path', '/')
|
||
|
|
|
||
|
|
if not sharing_id:
|
||
|
|
raise HTTPException(
|
||
|
|
status_code=400, detail="sharing_id là bắt buộc")
|
||
|
|
|
||
|
|
# CRITICAL FIX: Rate limiting to prevent Error 407 from rapid requests
|
||
|
|
current_time = time.time() * 1000 # Convert to milliseconds
|
||
|
|
last_time = _last_request_time[sharing_id]
|
||
|
|
time_since_last = current_time - last_time
|
||
|
|
|
||
|
|
if time_since_last < _rate_limit_window_ms:
|
||
|
|
wait_time = (_rate_limit_window_ms - time_since_last) / 1000
|
||
|
|
logger.warning(
|
||
|
|
f"Rate limit hit for {sharing_id}, rejecting request (wait {wait_time:.2f}s)")
|
||
|
|
raise HTTPException(
|
||
|
|
status_code=429,
|
||
|
|
detail=f"Vui lòng chậm lại, đợi {wait_time:.1f}s trước khi thao tác tiếp"
|
||
|
|
)
|
||
|
|
|
||
|
|
# Update last request time
|
||
|
|
_last_request_time[sharing_id] = current_time
|
||
|
|
|
||
|
|
# Get sharing worker instance
|
||
|
|
worker = nas_sharing_service.get_sharing_worker()
|
||
|
|
|
||
|
|
if not worker or not worker.driver:
|
||
|
|
raise HTTPException(
|
||
|
|
status_code=503, detail="Sharing worker không sẵn sàng")
|
||
|
|
|
||
|
|
# CRITICAL FIX: Lock driver to prevent race conditions
|
||
|
|
# Without lock, multiple users can navigate simultaneously and conflict
|
||
|
|
with worker.driver_lock:
|
||
|
|
# List folder using nas_sharing_api package
|
||
|
|
from ..services.nas_sharing_api import get_file_list
|
||
|
|
logger.debug(
|
||
|
|
f"📂 [Navigation] Lấy danh sách subfolder: {folder_path}")
|
||
|
|
files = get_file_list(
|
||
|
|
driver=worker.driver,
|
||
|
|
sharing_id=sharing_id,
|
||
|
|
folder_path=folder_path
|
||
|
|
)
|
||
|
|
|
||
|
|
logger.debug(
|
||
|
|
f"✅ [Navigation] Tìm thấy {len(files)} items trong: {folder_path}")
|
||
|
|
|
||
|
|
return {
|
||
|
|
"status": "success",
|
||
|
|
"files": files,
|
||
|
|
"path": folder_path,
|
||
|
|
"message": f"Tìm thấy {len(files)} item(s)"
|
||
|
|
}
|
||
|
|
|
||
|
|
except HTTPException:
|
||
|
|
raise
|
||
|
|
except Exception as e:
|
||
|
|
logger.error(f"Error listing sharing folder: {e}")
|
||
|
|
import traceback
|
||
|
|
traceback.print_exc()
|
||
|
|
raise HTTPException(status_code=500, detail=f"Lỗi hệ thống: {e}")
|
||
|
|
|
||
|
|
|
||
|
|
# ==================== DOWNLOAD ENDPOINTS ====================
|
||
|
|
|
||
|
|
@router.post('/download')
|
||
|
|
def download_sharing_files(payload: SharingLinkDownloadRequest):
|
||
|
|
"""
|
||
|
|
Download files from sharing link to NAS raw folder.
|
||
|
|
If ge_id and lang provided, files go to \\NAS_PATH\\{ge_id}_{lang}\\ (same as API download).
|
||
|
|
Otherwise, files go directly to \\NAS_PATH\\ root.
|
||
|
|
"""
|
||
|
|
try:
|
||
|
|
logger.debug(
|
||
|
|
f"Creating sharing link download job: {payload.sharing_id}, {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"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Calculate destination path
|
||
|
|
if payload.ge_id and payload.lang:
|
||
|
|
# Use same logic as API download: \\172.16.14.240\\raw\\11_US\\
|
||
|
|
destination_path = get_download_destination_path(
|
||
|
|
payload.ge_id, payload.lang)
|
||
|
|
ge_id_for_db = payload.ge_id
|
||
|
|
lang_for_db = payload.lang
|
||
|
|
else:
|
||
|
|
# Fallback: direct to root (backward compatible with old logic)
|
||
|
|
destination_path = nas_service.DESTINATION_PATH
|
||
|
|
ge_id_for_db = f"SHARING_{payload.sharing_id}"
|
||
|
|
lang_for_db = "LINK"
|
||
|
|
|
||
|
|
# ✅ FIX: Use downloads_service (NEW) instead of supabase_service (OLD)
|
||
|
|
result = downloads_service.create_downloads_batch(
|
||
|
|
files=payload.files,
|
||
|
|
ge_id=ge_id_for_db,
|
||
|
|
lang=lang_for_db,
|
||
|
|
mode='sharing', # Sharing link mode
|
||
|
|
sharing_id=payload.sharing_id,
|
||
|
|
mongodb_path=None, # No MongoDB path for sharing links
|
||
|
|
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 sharing 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'],
|
||
|
|
"destination_path": destination_path,
|
||
|
|
"sharing_id": payload.sharing_id
|
||
|
|
}
|
||
|
|
|
||
|
|
except Exception as e:
|
||
|
|
logger.error(f"Error creating sharing download job: {e}")
|
||
|
|
raise HTTPException(status_code=500, detail=f"Lỗi hệ thống: {e}")
|
||
|
|
|
||
|
|
|
||
|
|
# ==================== OTP HANDLING ====================
|
||
|
|
|
||
|
|
|
||
|
|
@router.get('/download-status/{batch_id}')
|
||
|
|
def get_sharing_download_status(batch_id: str):
|
||
|
|
"""
|
||
|
|
Get the status of a sharing link 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 sharing download job status: {e}")
|
||
|
|
raise HTTPException(status_code=500, detail=f"Lỗi hệ thống: {e}")
|
||
|
|
|
||
|
|
|
||
|
|
# ==================== OTP HANDLING ====================
|
||
|
|
|
||
|
|
@router.get('/otp-status')
|
||
|
|
def check_sharing_otp_status():
|
||
|
|
"""
|
||
|
|
Check if sharing worker is waiting for OTP
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
- otp_required: bool
|
||
|
|
"""
|
||
|
|
try:
|
||
|
|
otp_required = nas_sharing_service.is_otp_required()
|
||
|
|
return {
|
||
|
|
"otp_required": otp_required,
|
||
|
|
"message": "Vui lòng nhập mã OTP" if otp_required else "Không cần OTP"
|
||
|
|
}
|
||
|
|
except Exception as e:
|
||
|
|
logger.error(f"Error checking OTP status: {e}")
|
||
|
|
raise HTTPException(status_code=500, detail=f"Lỗi hệ thống: {e}")
|
||
|
|
|
||
|
|
|
||
|
|
@router.post('/submit-otp')
|
||
|
|
def submit_sharing_otp(payload: SharingOtpSubmit):
|
||
|
|
"""
|
||
|
|
Submit OTP code for sharing link login
|
||
|
|
|
||
|
|
Args:
|
||
|
|
otp_code: OTP code from user
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
- status: "success" | "error"
|
||
|
|
- message: Status message
|
||
|
|
"""
|
||
|
|
try:
|
||
|
|
status, message = nas_sharing_service.submit_otp(payload.otp_code)
|
||
|
|
|
||
|
|
if status == "success":
|
||
|
|
return {
|
||
|
|
"success": True,
|
||
|
|
"message": message
|
||
|
|
}
|
||
|
|
else:
|
||
|
|
raise HTTPException(status_code=400, detail=message)
|
||
|
|
|
||
|
|
except HTTPException:
|
||
|
|
raise
|
||
|
|
except Exception as e:
|
||
|
|
logger.error(f"Error submitting OTP: {e}")
|
||
|
|
raise HTTPException(status_code=500, detail=f"Lỗi hệ thống: {e}")
|