bmc_hub/app/services/anydesk_links.py

146 lines
9.2 KiB
Python
Raw Normal View History

"""Remember the customer end of an AnyDesk session, never the technician end."""
import json
import re
from fastapi import HTTPException
from app.core.database import execute_query
def customer_machine_id(device_info):
if isinstance(device_info, str):
device_info = json.loads(device_info)
value = re.sub(r'[\s-]', '', str((device_info or {}).get('to_id') or ''))
return value if re.fullmatch(r'[0-9]{1,64}', value) else None
def remember_machine(session_id, customer_id, contact_id, hardware_asset_id=None):
"""Caller owns the transaction including the selected session's update."""
rows = execute_query('SELECT device_info,hardware_asset_id FROM anydesk_sessions WHERE id=%s', (session_id,))
if not rows:
raise HTTPException(404, 'Sessionen findes ikke')
machine_id = customer_machine_id(rows[0]['device_info'])
if not machine_id:
raise HTTPException(409, 'Sessionen mangler et entydigt AnyDesk-ID for kundens maskine')
customer = execute_query('SELECT id FROM customers WHERE id=%s AND is_active=TRUE', (customer_id,))
if not customer:
raise HTTPException(409, 'Vælg et aktivt firma for at gemme AnyDesk-koblingen')
if contact_id is not None:
contact = execute_query('''SELECT c.id FROM contacts c
JOIN contact_companies cc ON cc.contact_id=c.id
WHERE c.id=%s AND cc.customer_id=%s AND c.is_active=TRUE''', (contact_id, customer_id))
if not contact:
raise HTTPException(409, 'Kontaktpersonen skal være aktiv og tilknyttet det valgte firma')
hardware_id = _remember_hardware(machine_id, customer_id, contact_id,
hardware_asset_id or rows[0].get('hardware_asset_id'))
execute_query('UPDATE anydesk_sessions SET hardware_asset_id=%s WHERE id=%s', (hardware_id, session_id))
return machine_id
def _remember_hardware(machine_id, customer_id, contact_id, selected_id):
# Serialize registrations of this ID so repeated/concurrent saves reuse one PC.
execute_query("SELECT pg_advisory_xact_lock(hashtextextended(%s,0))", ('anydesk-hardware:' + machine_id,))
matches = execute_query('''SELECT id,anydesk_id,current_owner_customer_id,current_owner_type
FROM hardware_assets WHERE deleted_at IS NULL
AND regexp_replace(COALESCE(anydesk_id,''),'[[:space:]-]','','g')=%s FOR UPDATE''', (machine_id,))
if len(matches) > 1:
raise HTTPException(409, 'AnyDesk-ID findes på flere hardwareposter. Ret dubletterne før registrering.')
if selected_id is not None:
selected = execute_query('''SELECT id,anydesk_id,current_owner_customer_id,current_owner_type
FROM hardware_assets WHERE id=%s AND deleted_at IS NULL FOR UPDATE''', (selected_id,))
if not selected:
raise HTTPException(409, 'Den valgte hardware findes ikke')
if matches and matches[0]['id'] != selected_id:
raise HTTPException(409, 'AnyDesk-ID findes allerede på en anden hardwarepost')
if selected[0].get('anydesk_id') and customer_machine_id({'to_id': selected[0]['anydesk_id']}) != machine_id:
raise HTTPException(409, 'Den valgte hardware har et andet AnyDesk-ID')
matches = selected
elif not matches and contact_id is not None:
matches = execute_query('''SELECT h.id,h.anydesk_id,h.current_owner_customer_id,h.current_owner_type
FROM hardware_assets h JOIN hardware_contacts hc ON hc.hardware_id=h.id
WHERE hc.contact_id=%s AND h.current_owner_customer_id=%s AND h.deleted_at IS NULL
AND h.status='active' AND h.asset_type IN ('pc','laptop')
AND NULLIF(BTRIM(h.anydesk_id),'') IS NULL FOR UPDATE OF h''', (contact_id, customer_id))
if len(matches) > 1:
raise HTTPException(409, 'Kontaktpersonen har flere computere uden AnyDesk-ID. Vælg den rigtige hardware først.')
if matches:
hardware = matches[0]
if hardware['current_owner_customer_id'] not in (None, customer_id):
raise HTTPException(409, 'Hardwaren tilhører et andet firma. Ret ejerskabet på hardwarekortet først.')
if hardware['current_owner_customer_id'] is None and hardware.get('current_owner_type') not in (None, 'customer'):
raise HTTPException(409, 'Hardwarens ejerskab skal afklares på hardwarekortet først.')
if contact_id is not None:
other_contacts = execute_query('''SELECT hc.contact_id FROM hardware_contacts hc
JOIN contacts c ON c.id=hc.contact_id AND c.is_active=TRUE
WHERE hc.hardware_id=%s AND hc.contact_id<>%s''', (hardware['id'], contact_id))
if other_contacts:
raise HTTPException(409, 'Hardwaren er koblet til en anden kontaktperson. Ret koblingen på hardwarekortet først.')
hardware_id = hardware['id']
execute_query('''UPDATE hardware_assets SET anydesk_id=%s,anydesk_link=%s,
current_owner_type=COALESCE(current_owner_type,'customer'),current_owner_customer_id=%s,updated_at=NOW() WHERE id=%s''',
(machine_id, 'anydesk:' + machine_id, customer_id, hardware_id))
else:
hardware_id = execute_query('''INSERT INTO hardware_assets
(asset_type,model,current_owner_type,current_owner_customer_id,status,anydesk_id,anydesk_link,notes)
VALUES('pc','PC','customer',%s,'active',%s,%s,'Oprettet fra AnyDesk-session') RETURNING id''',
(customer_id, machine_id, 'anydesk:' + machine_id))[0]['id']
if not matches or matches[0]['current_owner_customer_id'] is None:
execute_query('''INSERT INTO hardware_ownership_history
(hardware_id,owner_type,owner_customer_id,start_date,notes)
VALUES(%s,'customer',%s,CURRENT_DATE,'Registreret fra AnyDesk-session')''', (hardware_id, customer_id))
if contact_id is not None:
execute_query('''INSERT INTO hardware_contacts(hardware_id,contact_id,role,source)
VALUES(%s,%s,'primary','anydesk') ON CONFLICT(hardware_id,contact_id) DO NOTHING''',
(hardware_id, contact_id))
return hardware_id
def apply_machine_links(machine_id=None):
"""Hardware is authoritative. Ambiguous machine IDs are never auto-linked."""
rows = execute_query('''UPDATE anydesk_sessions s SET hardware_asset_id=COALESCE(s.hardware_asset_id,m.id),
customer_id=COALESCE(s.customer_id,m.customer_id),contact_id=COALESCE(s.contact_id,m.contact_id),updated_at=NOW()
FROM (WITH hardware AS (
SELECT regexp_replace(anydesk_id,'[[:space:]-]','','g') AS machine,
MIN(id) AS id
FROM hardware_assets WHERE deleted_at IS NULL AND anydesk_id IS NOT NULL
GROUP BY regexp_replace(anydesk_id,'[[:space:]-]','','g') HAVING COUNT(*)=1
), links AS (
SELECT h.id,h.current_owner_customer_id AS customer_id,hw.machine,
CASE WHEN COUNT(DISTINCT con.id)=1 THEN MIN(con.id) ELSE NULL END AS contact_id
FROM hardware hw JOIN hardware_assets h ON h.id=hw.id
JOIN customers c ON c.id=h.current_owner_customer_id AND c.is_active=TRUE
LEFT JOIN hardware_contacts hc ON hc.hardware_id=h.id
LEFT JOIN contacts con ON con.id=hc.contact_id AND con.is_active=TRUE AND EXISTS (
SELECT 1 FROM contact_companies cc WHERE cc.contact_id=con.id AND cc.customer_id=c.id)
WHERE hw.machine ~ '^[0-9]+$' AND (%s IS NULL OR hw.machine=%s)
GROUP BY h.id,h.current_owner_customer_id,hw.machine
) SELECT * FROM links) m
WHERE regexp_replace(COALESCE(s.device_info->>'to_id',''),'[[:space:]-]','','g')=m.machine
AND (s.hardware_asset_id IS NULL OR s.hardware_asset_id=m.id)
AND ((s.customer_id IS NULL AND s.contact_id IS NULL) OR s.customer_id=m.customer_id)
AND (s.hardware_asset_id IS NULL OR s.customer_id IS NULL OR (s.contact_id IS NULL AND m.contact_id IS NOT NULL))
RETURNING s.id''', (machine_id, machine_id))
return len(rows or []) + _apply_legacy_links(machine_id)
def _apply_legacy_links(machine_id=None):
"""Fill only unassigned sessions or a missing contact for the same customer."""
table = execute_query("SELECT to_regclass('anydesk_machine_links') AS present")
if not table or not table[0]['present']:
return 0
rows = execute_query('''UPDATE anydesk_sessions s SET
customer_id=COALESCE(s.customer_id,m.customer_id),
contact_id=COALESCE(s.contact_id,m.contact_id),updated_at=NOW()
FROM anydesk_machine_links m JOIN customers c ON c.id=m.customer_id AND c.is_active=TRUE
LEFT JOIN contacts con ON con.id=m.contact_id
WHERE regexp_replace(COALESCE(s.device_info->>'to_id',''), '[[:space:]-]', '', 'g')=m.anydesk_id
AND (%s IS NULL OR m.anydesk_id=%s)
AND NOT EXISTS (SELECT 1 FROM hardware_assets h WHERE h.deleted_at IS NULL
AND regexp_replace(COALESCE(h.anydesk_id,''),'[[:space:]-]','','g')=m.anydesk_id)
AND (m.contact_id IS NULL OR (con.is_active=TRUE AND EXISTS (
SELECT 1 FROM contact_companies cc WHERE cc.contact_id=m.contact_id AND cc.customer_id=m.customer_id)))
AND ((s.customer_id IS NULL AND s.contact_id IS NULL)
OR (s.customer_id=m.customer_id AND s.contact_id IS NULL AND m.contact_id IS NOT NULL))
RETURNING s.id''', (machine_id, machine_id))
return len(rows or [])