Files
timemaster/backend/app/services/kiosk_service.py
T
sysops 1fedd683e0 Initial commit – TimeMaster Zeiterfassung & HR-Tool
Stand: agent-06 (Audit-Log), agent-05 (Krankmeldung), agent-07 Phase 1 (Personalnummer),
Busylight-Pull-Integration, TOTP/2FA, Abwesenheiten, Zeiterfassung, Kiosk-Grundgerüst.
Migrations 0001–0023 deployed auf 192.168.1.137 + .164.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 20:03:27 +02:00

88 lines
3.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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()