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>
This commit is contained in:
sysops
2026-05-23 20:03:27 +02:00
commit 1fedd683e0
178 changed files with 29896 additions and 0 deletions
+102
View File
@@ -0,0 +1,102 @@
from uuid import UUID
from fastapi import APIRouter, Depends, Header, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.database import get_db
from app.core.dependencies import require_role
from app.models.user import User, UserRole
from app.schemas.kiosk import KioskDeviceCreate, KioskDeviceCreated, KioskDeviceOut, KioskDeviceUpdate
from app.services.kiosk_service import kiosk_service
router = APIRouter(prefix="/kiosk", tags=["Kiosk"])
_admin_roles = (UserRole.COMPANY_ADMIN, UserRole.SUPER_ADMIN)
# ── Geräteverwaltung (COMPANY_ADMIN) ──────────────────────────────────────────
@router.get("/devices", response_model=list[KioskDeviceOut])
async def list_devices(
current_user: User = require_role(*_admin_roles),
db: AsyncSession = Depends(get_db),
):
"""Alle registrierten Kiosk-Geräte der Firma auflisten."""
return await kiosk_service.list_devices(current_user.company_id, db)
@router.post("/devices", response_model=KioskDeviceCreated, status_code=201)
async def create_device(
data: KioskDeviceCreate,
current_user: User = require_role(*_admin_roles),
db: AsyncSession = Depends(get_db),
):
"""Neues Kiosk-Gerät registrieren. Token wird nur einmalig zurückgegeben."""
device, raw_token = await kiosk_service.create_device(current_user.company_id, data, db)
await db.commit()
await db.refresh(device)
return KioskDeviceCreated(
**KioskDeviceOut.model_validate(device).model_dump(),
token=raw_token,
)
@router.get("/devices/{device_id}", response_model=KioskDeviceOut)
async def get_device(
device_id: UUID,
current_user: User = require_role(*_admin_roles),
db: AsyncSession = Depends(get_db),
):
return await kiosk_service.get_device(device_id, current_user.company_id, db)
@router.patch("/devices/{device_id}", response_model=KioskDeviceOut)
async def update_device(
device_id: UUID,
data: KioskDeviceUpdate,
current_user: User = require_role(*_admin_roles),
db: AsyncSession = Depends(get_db),
):
device = await kiosk_service.update_device(device_id, current_user.company_id, data, db)
await db.commit()
await db.refresh(device)
return KioskDeviceOut.model_validate(device)
@router.post("/devices/{device_id}/rotate-token", response_model=KioskDeviceCreated)
async def rotate_token(
device_id: UUID,
current_user: User = require_role(*_admin_roles),
db: AsyncSession = Depends(get_db),
):
"""Token rotieren das alte Token wird sofort ungültig."""
device, raw_token = await kiosk_service.rotate_token(device_id, current_user.company_id, db)
await db.commit()
await db.refresh(device)
return KioskDeviceCreated(
**KioskDeviceOut.model_validate(device).model_dump(),
token=raw_token,
)
@router.delete("/devices/{device_id}", status_code=204)
async def delete_device(
device_id: UUID,
current_user: User = require_role(*_admin_roles),
db: AsyncSession = Depends(get_db),
):
await kiosk_service.delete_device(device_id, current_user.company_id, db)
await db.commit()
# ── Kiosk-Auth (Gerät authentifiziert sich per Token) ─────────────────────────
@router.get("/me", response_model=KioskDeviceOut)
async def kiosk_me(
x_kiosk_token: str = Header(..., alias="X-Kiosk-Token", min_length=32, max_length=128),
db: AsyncSession = Depends(get_db),
):
"""Kiosk-Gerät prüft seine eigene Identität / aktualisiert last_seen_at."""
device = await kiosk_service.authenticate_device(x_kiosk_token, db)
await db.commit()
return KioskDeviceOut.model_validate(device)