""" Device Service - Manages device registration and operations """ from typing import Optional, List from datetime import datetime from models.device import ( Device, DeviceCreate, DeviceUpdate, DeviceResponse, DeviceStatus, DevicePlatform ) from utils.security import security_manager from utils.device_fingerprint import DeviceFingerprint class DeviceService: """ Service for managing registered devices Handles: - Device registration - Device lookup - Device revocation - Device fingerprint validation """ def __init__(self, db_pool=None): self.db = db_pool async def register_device(self, device_data: DeviceCreate) -> Device: """ Register a new device Args: device_data: Device registration data Returns: Device: Created device """ # Hash the device fingerprint for storage fingerprint_hash = security_manager.hash_fingerprint(device_data.device_fingerprint) device = Device( user_id=device_data.user_id, device_name=device_data.device_name, platform=device_data.platform, public_key=device_data.public_key, device_fingerprint=fingerprint_hash, push_token=device_data.push_token, status=DeviceStatus.ACTIVE, created_at=datetime.utcnow(), updated_at=datetime.utcnow() ) # Store in database if self.db: await self._save_device_to_db(device) return device async def get_device(self, device_id: str) -> Optional[Device]: """Get device by ID""" if not self.db: return None row = await self.db.fetchrow( """ SELECT * FROM devices WHERE id = $1 """, device_id ) if row: return Device(**dict(row)) return None async def get_device_by_fingerprint( self, user_id: str, fingerprint: str ) -> Optional[Device]: """Get device by user ID and fingerprint""" if not self.db: return None fingerprint_hash = security_manager.hash_fingerprint(fingerprint) row = await self.db.fetchrow( """ SELECT * FROM devices WHERE user_id = $1 AND device_fingerprint = $2 AND status = 'active' """, user_id, fingerprint_hash ) if row: return Device(**dict(row)) return None async def get_user_devices(self, user_id: str) -> List[DeviceResponse]: """Get all devices for a user""" if not self.db: return [] rows = await self.db.fetch( """ SELECT id, user_id, device_name, platform, status, created_at, last_used_at FROM devices WHERE user_id = $1 ORDER BY last_used_at DESC NULLS LAST """, user_id ) return [DeviceResponse(**dict(row)) for row in rows] async def update_device( self, device_id: str, update_data: DeviceUpdate ) -> Optional[Device]: """Update device information""" device = await self.get_device(device_id) if not device: return None # Update fields if update_data.device_name: device.device_name = update_data.device_name if update_data.push_token is not None: device.push_token = update_data.push_token if update_data.status: device.status = update_data.status device.updated_at = datetime.utcnow() if self.db: await self._update_device_in_db(device) return device async def revoke_device(self, device_id: str, reason: str = "") -> bool: """ Revoke a device Args: device_id: Device to revoke reason: Reason for revocation Returns: bool: True if revoked successfully """ device = await self.get_device(device_id) if not device: return False device.status = DeviceStatus.REVOKED device.updated_at = datetime.utcnow() if self.db: await self.db.execute( """ UPDATE devices SET status = 'revoked', updated_at = NOW() WHERE id = $1 """, device_id ) return True async def validate_device( self, device_id: str, fingerprint: Optional[str] = None ) -> bool: """ Validate device is active and optionally check fingerprint Args: device_id: Device ID fingerprint: Optional fingerprint to validate Returns: bool: True if device is valid """ device = await self.get_device(device_id) if not device: return False if device.status != DeviceStatus.ACTIVE: return False if fingerprint: fingerprint_hash = security_manager.hash_fingerprint(fingerprint) if device.device_fingerprint != fingerprint_hash: return False return True async def update_last_used(self, device_id: str, ip_address: Optional[str] = None): """Update last used timestamp""" if self.db: await self.db.execute( """ UPDATE devices SET last_used_at = NOW(), last_ip = $2 WHERE id = $1 """, device_id, ip_address ) async def _save_device_to_db(self, device: Device): """Save device to database""" await self.db.execute( """ INSERT INTO devices ( id, user_id, device_name, platform, public_key, device_fingerprint, push_token, status, created_at, updated_at ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) """, device.id, device.user_id, device.device_name, device.platform.value, device.public_key, device.device_fingerprint, device.push_token, device.status.value, device.created_at, device.updated_at ) async def _update_device_in_db(self, device: Device): """Update device in database""" await self.db.execute( """ UPDATE devices SET device_name = $2, push_token = $3, status = $4, updated_at = $5 WHERE id = $1 """, device.id, device.device_name, device.push_token, device.status.value, device.updated_at ) # Global device service instance device_service = DeviceService()