import secrets from datetime import datetime, timezone from uuid import UUID from fastapi import HTTPException from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.core.security import hash_token from app.models.kiosk_device import KioskDevice from app.schemas.kiosk import KioskDeviceCreate, KioskDeviceUpdate class KioskService: async def list_devices(self, company_id: UUID, db: AsyncSession) -> list[KioskDevice]: result = await db.scalars( select(KioskDevice) .where(KioskDevice.company_id == company_id) .order_by(KioskDevice.created_at.desc()) ) return list(result.all()) async def create_device( self, company_id: UUID, data: KioskDeviceCreate, db: AsyncSession ) -> tuple[KioskDevice, str]: """Gerät anlegen. Gibt (device, raw_token) zurück – raw_token nur einmalig.""" raw_token = secrets.token_urlsafe(48) device = KioskDevice( company_id=company_id, name=data.name, location=data.location, token_hash=hash_token(raw_token), ) db.add(device) await db.flush() return device, raw_token async def get_device(self, device_id: UUID, company_id: UUID, db: AsyncSession) -> KioskDevice: device = await db.scalar( select(KioskDevice).where( KioskDevice.id == device_id, KioskDevice.company_id == company_id, ) ) if device is None: raise HTTPException(status_code=404, detail="Gerät nicht gefunden.") return device async def update_device( self, device_id: UUID, company_id: UUID, data: KioskDeviceUpdate, db: AsyncSession ) -> KioskDevice: device = await self.get_device(device_id, company_id, db) changes = data.model_dump(exclude_none=True) for field, value in changes.items(): setattr(device, field, value) return device async def rotate_token( self, device_id: UUID, company_id: UUID, db: AsyncSession ) -> tuple[KioskDevice, str]: """Token rotieren – altes Token wird sofort ungültig.""" device = await self.get_device(device_id, company_id, db) raw_token = secrets.token_urlsafe(48) device.token_hash = hash_token(raw_token) return device, raw_token async def delete_device(self, device_id: UUID, company_id: UUID, db: AsyncSession) -> None: device = await self.get_device(device_id, company_id, db) await db.delete(device) async def authenticate_device(self, raw_token: str, db: AsyncSession) -> KioskDevice: """Gerät per Token authentifizieren (für Kiosk-Endpoints).""" token_hash = hash_token(raw_token) device = await db.scalar( select(KioskDevice).where( KioskDevice.token_hash == token_hash, KioskDevice.is_active.is_(True), ) ) if device is None: raise HTTPException(status_code=401, detail="Ungültiges oder deaktiviertes Gerät.") device.last_seen_at = datetime.now(timezone.utc) return device kiosk_service = KioskService()