richtext's picture
Upload folder using huggingface_hub
3674b4b verified
"""Box integration API endpoints."""
from fastapi import APIRouter, HTTPException, status
from pydantic import BaseModel
from typing import Optional
from backend.config import settings
from backend.core.dependencies import DbSession, AdminUser
from backend.services.box_integration import BoxService, is_box_configured
from backend.services.box_worker import (
run_box_sync, get_sync_status, update_sync_schedule,
start_scheduler, stop_scheduler
)
from backend.models import AuditLog
router = APIRouter()
def check_demo_mode():
"""Raise an error if running in demo mode."""
if settings.demo_mode:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Box integration is disabled in demo mode. See the feature preview below for what's available in production."
)
# Request/Response models
class OAuthCallbackRequest(BaseModel):
code: str
state: str
class FolderConfigRequest(BaseModel):
staging_folder_id: str
staging_folder_name: str
processed_folder_id: str
processed_folder_name: str
sync_interval_minutes: int = 60
class ConnectionStatus(BaseModel):
is_configured: bool
is_connected: bool
is_active: bool
box_user_name: Optional[str] = None
box_user_email: Optional[str] = None
staging_folder_name: Optional[str] = None
processed_folder_name: Optional[str] = None
sync_interval_minutes: int = 60
last_sync: Optional[str] = None
last_sync_status: Optional[str] = None
last_sync_message: Optional[str] = None
files_processed_count: int = 0
# Backup config
backup_folder_id: Optional[str] = None
backup_folder_name: Optional[str] = None
backup_enabled: bool = False
backup_schedule: Optional[str] = None
backup_time: Optional[str] = None
last_backup: Optional[str] = None
last_backup_status: Optional[str] = None
last_backup_message: Optional[str] = None
class BackupConfigRequest(BaseModel):
backup_folder_id: str
backup_folder_name: str
backup_enabled: bool = True
backup_schedule: str = "manual" # manual, daily, weekly
backup_time: Optional[str] = None # HH:MM format for scheduled backups
@router.get("/status")
def get_box_status(admin: AdminUser, db: DbSession) -> ConnectionStatus:
"""Get Box connection status including backup configuration."""
box_service = BoxService(db)
connection = box_service.get_connection()
if not connection:
return ConnectionStatus(
is_configured=is_box_configured(),
is_connected=False,
is_active=False
)
return ConnectionStatus(
is_configured=is_box_configured(),
is_connected=True,
is_active=connection.is_active,
box_user_name=connection.box_user_name,
box_user_email=connection.box_user_email,
staging_folder_name=connection.staging_folder_name,
processed_folder_name=connection.processed_folder_name,
sync_interval_minutes=connection.sync_interval_minutes,
last_sync=connection.last_sync.isoformat() if connection.last_sync else None,
last_sync_status=connection.last_sync_status,
last_sync_message=connection.last_sync_message,
files_processed_count=connection.files_processed_count,
# Backup fields
backup_folder_id=connection.backup_folder_id,
backup_folder_name=connection.backup_folder_name,
backup_enabled=connection.backup_enabled,
backup_schedule=connection.backup_schedule,
backup_time=connection.backup_time,
last_backup=connection.last_backup.isoformat() if connection.last_backup else None,
last_backup_status=connection.last_backup_status,
last_backup_message=connection.last_backup_message
)
@router.get("/auth-url")
def get_auth_url(admin: AdminUser, db: DbSession):
"""Get Box OAuth authorization URL."""
check_demo_mode()
if not is_box_configured():
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Box OAuth not configured. Set BOX_CLIENT_ID and BOX_CLIENT_SECRET in .env"
)
box_service = BoxService(db)
try:
result = box_service.get_oauth_url()
return result
except ValueError as e:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
@router.post("/callback")
def oauth_callback(request: OAuthCallbackRequest, admin: AdminUser, db: DbSession):
"""Handle Box OAuth callback - exchange code for tokens."""
box_service = BoxService(db)
try:
connection = box_service.exchange_code(request.code, request.state)
# Log audit event
audit = AuditLog(
user_id=admin.id,
action='box_connect',
resource_type='box_connection',
resource_id=connection.id,
details={
'box_user_email': connection.box_user_email,
'box_user_name': connection.box_user_name
}
)
db.add(audit)
db.commit()
return {
'success': True,
'message': f'Connected to Box as {connection.box_user_name}',
'user_name': connection.box_user_name,
'user_email': connection.box_user_email
}
except ValueError as e:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
@router.get("/folders")
def list_folders(admin: AdminUser, db: DbSession, folder_id: str = '0'):
"""List folders in Box (for folder picker)."""
box_service = BoxService(db)
try:
folders = box_service.list_folders(folder_id)
return {'folders': folders, 'parent_id': folder_id}
except ValueError as e:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
@router.post("/configure")
def configure_folders(request: FolderConfigRequest, admin: AdminUser, db: DbSession):
"""Configure Box folders for staging and processed files."""
box_service = BoxService(db)
try:
connection = box_service.update_folder_config(
staging_folder_id=request.staging_folder_id,
staging_folder_name=request.staging_folder_name,
processed_folder_id=request.processed_folder_id,
processed_folder_name=request.processed_folder_name,
sync_interval_minutes=request.sync_interval_minutes
)
# Update scheduler with new interval
update_sync_schedule(request.sync_interval_minutes)
# Log audit event
audit = AuditLog(
user_id=admin.id,
action='box_configure',
resource_type='box_connection',
resource_id=connection.id,
details={
'staging_folder': request.staging_folder_name,
'processed_folder': request.processed_folder_name,
'sync_interval': request.sync_interval_minutes
}
)
db.add(audit)
db.commit()
return {
'success': True,
'message': 'Box folders configured successfully',
'is_active': connection.is_active
}
except ValueError as e:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
@router.post("/disconnect")
def disconnect_box(admin: AdminUser, db: DbSession):
"""Disconnect Box account."""
box_service = BoxService(db)
# Log before disconnect to capture connection details
connection = box_service.get_connection()
if connection:
audit = AuditLog(
user_id=admin.id,
action='box_disconnect',
resource_type='box_connection',
resource_id=connection.id,
details={
'box_user_email': connection.box_user_email
}
)
db.add(audit)
if box_service.disconnect():
# Stop scheduler
stop_scheduler()
db.commit()
return {'success': True, 'message': 'Box disconnected'}
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="No Box connection found")
@router.post("/sync")
def trigger_sync(admin: AdminUser, db: DbSession):
"""Manually trigger Box sync."""
status_info = get_sync_status()
if not status_info.get('is_connected'):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="No Box connection available"
)
if not status_info.get('is_active'):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Box connection not active. Please configure folders first."
)
# Log sync trigger
box_service = BoxService(db)
connection = box_service.get_connection()
if connection:
audit = AuditLog(
user_id=admin.id,
action='box_sync_manual',
resource_type='box_connection',
resource_id=connection.id
)
db.add(audit)
db.commit()
# Run sync
result = run_box_sync(force=True)
return result
@router.get("/sync-status")
def get_current_sync_status(admin: AdminUser, db: DbSession):
"""Get current sync worker status."""
return get_sync_status()
@router.get("/logs")
def get_sync_logs(admin: AdminUser, db: DbSession, limit: int = 20):
"""Get recent sync logs."""
box_service = BoxService(db)
logs = box_service.get_sync_logs(limit)
return {
'logs': [
{
'id': log.id,
'started_at': log.started_at.isoformat() if log.started_at else None,
'completed_at': log.completed_at.isoformat() if log.completed_at else None,
'status': log.status,
'files_found': log.files_found,
'files_processed': log.files_processed,
'files_failed': log.files_failed,
'records_imported': log.records_imported,
'error_message': log.error_message
}
for log in logs
]
}
# ============== Database Backup to Box ==============
@router.post("/backup/configure")
def configure_backup(request: BackupConfigRequest, admin: AdminUser, db: DbSession):
"""Configure database backup to Box."""
box_service = BoxService(db)
connection = box_service.get_connection()
if not connection:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="No Box connection available. Please connect to Box first."
)
# Update backup configuration
connection.backup_folder_id = request.backup_folder_id
connection.backup_folder_name = request.backup_folder_name
connection.backup_enabled = request.backup_enabled
connection.backup_schedule = request.backup_schedule
connection.backup_time = request.backup_time
db.commit()
# Update backup scheduler if enabled
from backend.services.box_worker import update_backup_schedule
if request.backup_enabled and request.backup_schedule != "manual":
update_backup_schedule(request.backup_schedule, request.backup_time)
else:
# Disable backup scheduler
update_backup_schedule(None, None)
# Audit log
audit = AuditLog(
user_id=admin.id,
action='backup_configured',
resource_type='box_connection',
resource_id=connection.id,
details={
'backup_folder': request.backup_folder_name,
'schedule': request.backup_schedule,
'enabled': request.backup_enabled
}
)
db.add(audit)
db.commit()
return {
'success': True,
'message': 'Backup configuration saved',
'backup_enabled': connection.backup_enabled,
'backup_schedule': connection.backup_schedule
}
@router.post("/backup/run")
def run_backup(admin: AdminUser, db: DbSession):
"""Manually trigger database backup to Box."""
box_service = BoxService(db)
connection = box_service.get_connection()
if not connection:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="No Box connection available"
)
if not connection.backup_folder_id:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Backup folder not configured. Please select a backup folder first."
)
# Run backup
from backend.services.box_worker import run_database_backup
result = run_database_backup(triggered_by=admin.email)
# Audit log
audit = AuditLog(
user_id=admin.id,
action='backup_manual',
resource_type='database',
details={
'status': result.get('status'),
'backup_folder': connection.backup_folder_name,
'filename': result.get('filename')
}
)
db.add(audit)
db.commit()
return result
@router.delete("/backup/configure")
def disable_backup(admin: AdminUser, db: DbSession):
"""Disable database backup to Box."""
box_service = BoxService(db)
connection = box_service.get_connection()
if not connection:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="No Box connection found"
)
# Clear backup configuration
connection.backup_folder_id = None
connection.backup_folder_name = None
connection.backup_enabled = False
connection.backup_schedule = None
connection.backup_time = None
db.commit()
# Disable backup scheduler
from backend.services.box_worker import update_backup_schedule
update_backup_schedule(None, None)
# Audit log
audit = AuditLog(
user_id=admin.id,
action='backup_disabled',
resource_type='box_connection',
resource_id=connection.id
)
db.add(audit)
db.commit()
return {'success': True, 'message': 'Backup disabled'}