bmc_hub/app/routers/anydesk.py

380 lines
13 KiB
Python
Raw Normal View History

"""
AnyDesk Remote Support Router
REST API endpoints for managing remote support sessions
"""
import logging
from typing import Optional
from fastapi import APIRouter, HTTPException
from fastapi.responses import JSONResponse
from app.models.schemas import (
AnyDeskSessionCreate,
AnyDeskSession,
AnyDeskSessionDetail,
AnyDeskSessionHistory,
AnyDeskSessionWithWorklog
)
from app.services.anydesk import AnyDeskService
from app.core.database import execute_query
logger = logging.getLogger(__name__)
router = APIRouter()
anydesk_service = AnyDeskService()
# =====================================================
# Session Management Endpoints
# =====================================================
@router.post("/anydesk/start-session", response_model=AnyDeskSession, tags=["Remote Support"])
async def start_remote_session(session_data: AnyDeskSessionCreate):
"""
Start a new AnyDesk remote support session
- **customer_id**: Required - Customer to provide support to
- **contact_id**: Optional - Specific contact person
- **sag_id**: Optional - Link to case/ticket for time tracking
- **description**: Optional - Purpose of session
- **created_by_user_id**: Optional - User initiating session
Returns session details with access link for sharing with customer
"""
try:
logger.info(f"🔗 Starting AnyDesk session for customer {session_data.customer_id}")
# Verify customer exists
cust_query = "SELECT id FROM customers WHERE id = %s"
customer = execute_query(cust_query, (session_data.customer_id,))
if not customer:
raise HTTPException(status_code=404, detail="Customer not found")
# Verify contact exists if provided
if session_data.contact_id:
contact_query = "SELECT id FROM contacts WHERE id = %s"
contact = execute_query(contact_query, (session_data.contact_id,))
if not contact:
raise HTTPException(status_code=404, detail="Contact not found")
# Verify sag exists if provided
if session_data.sag_id:
sag_query = "SELECT id FROM sag_sager WHERE id = %s"
sag = execute_query(sag_query, (session_data.sag_id,))
if not sag:
raise HTTPException(status_code=404, detail="Case not found")
# Create session via AnyDesk service
result = await anydesk_service.create_session(
customer_id=session_data.customer_id,
contact_id=session_data.contact_id,
sag_id=session_data.sag_id,
description=session_data.description,
created_by_user_id=session_data.created_by_user_id
)
if "error" in result:
raise HTTPException(status_code=400, detail=result["error"])
return result
except HTTPException:
raise
except Exception as e:
logger.error(f"Error starting session: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@router.get("/anydesk/sessions/{session_id}", response_model=AnyDeskSessionDetail, tags=["Remote Support"])
async def get_session_details(session_id: int):
"""
Get details of a specific AnyDesk session
Includes current status, duration, and linked entities (contact, customer, case)
"""
try:
query = """
SELECT
s.id, s.anydesk_session_id, s.customer_id, s.contact_id, s.sag_id,
s.session_link, s.status, s.started_at, s.ended_at, s.duration_minutes,
s.created_by_user_id, s.created_at, s.updated_at,
c.first_name || ' ' || c.last_name as contact_name,
cust.name as customer_name,
sag.title as sag_title,
u.full_name as created_by_user_name,
s.device_info, s.metadata
FROM anydesk_sessions s
LEFT JOIN contacts c ON s.contact_id = c.id
LEFT JOIN customers cust ON s.customer_id = cust.id
LEFT JOIN sag_sager sag ON s.sag_id = sag.id
LEFT JOIN users u ON s.created_by_user_id = u.id
WHERE s.id = %s
"""
result = execute_query(query, (session_id,))
if not result:
raise HTTPException(status_code=404, detail="Session not found")
session = result[0]
return AnyDeskSessionDetail(**session)
except HTTPException:
raise
except Exception as e:
logger.error(f"Error fetching session: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@router.post("/anydesk/sessions/{session_id}/end", tags=["Remote Support"])
async def end_remote_session(session_id: int):
"""
End a remote support session and calculate duration
Returns completed session with duration in minutes and hours
"""
try:
logger.info(f"🛑 Ending AnyDesk session {session_id}")
result = await anydesk_service.end_session(session_id)
if "error" in result:
raise HTTPException(status_code=400, detail=result["error"])
return JSONResponse(content=result)
except HTTPException:
raise
except Exception as e:
logger.error(f"Error ending session: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@router.get("/anydesk/sessions", response_model=AnyDeskSessionHistory, tags=["Remote Support"])
async def get_session_history(
contact_id: Optional[int] = None,
customer_id: Optional[int] = None,
sag_id: Optional[int] = None,
limit: int = 50,
offset: int = 0
):
"""
Get session history filtered by contact, customer, or case
At least one filter parameter should be provided.
Results are paginated and sorted by date (newest first).
- **contact_id**: Get all sessions for a specific contact
- **customer_id**: Get all sessions for a company
- **sag_id**: Get sessions linked to a specific case
- **limit**: Number of sessions per page (default 50, max 100)
- **offset**: Pagination offset
"""
try:
if not any([contact_id, customer_id, sag_id]):
raise HTTPException(
status_code=400,
detail="At least one filter (contact_id, customer_id, or sag_id) is required"
)
# Validate limit
if limit > 100:
limit = 100
result = await anydesk_service.get_session_history(
contact_id=contact_id,
customer_id=customer_id,
sag_id=sag_id,
limit=limit,
offset=offset
)
if "error" in result:
raise HTTPException(status_code=400, detail=result["error"])
# Enrich session data with contact/customer/user names
enriched_sessions = []
for session in result.get("sessions", []):
enriched_sessions.append(AnyDeskSessionDetail(**session))
return AnyDeskSessionHistory(
sessions=enriched_sessions,
total=result["total"],
limit=limit,
offset=offset
)
except HTTPException:
raise
except Exception as e:
logger.error(f"Error fetching session history: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
# =====================================================
# Worklog Integration Endpoints
# =====================================================
@router.get("/anydesk/sessions/{session_id}/worklog-suggestion", tags=["Remote Support", "Time Tracking"])
async def suggest_worklog_from_session(session_id: int):
"""
Get suggested worklog entry from a completed session
Calculates billable hours from session duration and provides
a pre-filled worklog suggestion for review/approval.
The worklog still needs to be created separately via the
timetracking/worklog endpoints after user approval.
"""
try:
# Get session
query = """
SELECT id, duration_minutes, customer_id, sag_id, contact_id,
started_at, ended_at, status
FROM anydesk_sessions
WHERE id = %s
"""
result = execute_query(query, (session_id,))
if not result:
raise HTTPException(status_code=404, detail="Session not found")
session = result[0]
if session["status"] != "completed":
raise HTTPException(
status_code=400,
detail=f"Cannot suggest worklog for non-completed session (status: {session['status']})"
)
if not session["duration_minutes"]:
raise HTTPException(
status_code=400,
detail="Session duration not calculated yet"
)
# Build worklog suggestion
duration_hours = round(session["duration_minutes"] / 60, 2)
suggestion = {
"session_id": session_id,
"duration_minutes": session["duration_minutes"],
"duration_hours": duration_hours,
"start_time": str(session["started_at"]),
"end_time": str(session["ended_at"]),
"description": f"Remote support session via AnyDesk",
"work_type": "remote_support",
"billable": True,
"linked_to": {
"customer_id": session["customer_id"],
"contact_id": session["contact_id"],
"sag_id": session["sag_id"]
}
}
logger.info(f"Generated worklog suggestion for session {session_id}: {duration_hours}h")
return JSONResponse(content=suggestion)
except HTTPException:
raise
except Exception as e:
logger.error(f"Error generating worklog suggestion: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
# =====================================================
# Status & Analytics Endpoints
# =====================================================
@router.get("/anydesk/stats", tags=["Remote Support", "Analytics"])
async def get_anydesk_stats():
"""
Get AnyDesk integration statistics
- Total sessions today/this week/this month
- Active sessions count
- Average session duration
- Most supported customers
"""
try:
stats = {
"sessions_today": 0,
"sessions_this_week": 0,
"sessions_this_month": 0,
"active_sessions": 0,
"average_duration_minutes": 0,
"total_support_hours": 0
}
# Get today's sessions
query = """
SELECT COUNT(*) as count FROM anydesk_sessions
WHERE DATE(started_at) = CURRENT_DATE
"""
result = execute_query(query)
stats["sessions_today"] = result[0]["count"] if result else 0
# Get this week's sessions
query = """
SELECT COUNT(*) as count FROM anydesk_sessions
WHERE started_at >= CURRENT_DATE - INTERVAL '7 days'
"""
result = execute_query(query)
stats["sessions_this_week"] = result[0]["count"] if result else 0
# Get this month's sessions
query = """
SELECT COUNT(*) as count FROM anydesk_sessions
WHERE started_at >= DATE_TRUNC('month', CURRENT_DATE)
"""
result = execute_query(query)
stats["sessions_this_month"] = result[0]["count"] if result else 0
# Get active sessions
query = """
SELECT COUNT(*) as count FROM anydesk_sessions
WHERE status = 'active'
"""
result = execute_query(query)
stats["active_sessions"] = result[0]["count"] if result else 0
# Get average duration
query = """
SELECT AVG(duration_minutes) as avg_duration FROM anydesk_sessions
WHERE status = 'completed' AND duration_minutes IS NOT NULL
"""
result = execute_query(query)
stats["average_duration_minutes"] = round(result[0]["avg_duration"], 1) if result and result[0]["avg_duration"] else 0
# Get total support hours this month
query = """
SELECT SUM(duration_minutes) as total_minutes FROM anydesk_sessions
WHERE status = 'completed'
AND started_at >= DATE_TRUNC('month', CURRENT_DATE)
"""
result = execute_query(query)
total_minutes = result[0]["total_minutes"] if result and result[0]["total_minutes"] else 0
stats["total_support_hours"] = round(total_minutes / 60, 2)
return JSONResponse(content=stats)
except Exception as e:
logger.error(f"Error calculating stats: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@router.get("/anydesk/health", tags=["Remote Support", "Health"])
async def anydesk_health_check():
"""
Health check for AnyDesk integration
Returns configuration status, API connectivity, and last sync time
"""
return JSONResponse(content={
"service": "AnyDesk Remote Support",
"status": "operational",
"configured": bool(anydesk_service.api_token and anydesk_service.license_id),
"dry_run_mode": anydesk_service.dry_run,
"read_only_mode": anydesk_service.read_only,
"auto_start_enabled": anydesk_service.auto_start
})