# MDM Device Link für Magic App — Design

## Ziel
In der ReBreak Magic App unter „Meine iOS-Geräte“ automatisch prüfen, ob ein iOS-Gerät im NanoMDM enrolled ist, und den passenden Status in der Device-Card anzeigen.

## Annahmen / Einschränkungen
- Native-App-DB (`rebreak` auf `rebreak-server`) und NanoMDM-DB (`nanomdm` auf `rebreak-mdm`) liegen auf **unterschiedlichen Hetzner-VPS**.
- Apple gibt die Hardware-UDID **nicht** an React Native / Expo Apps weiter. Die Native-App-`deviceId` ist `identifierForVendor`, nicht die NanoMDM-UDID.
- Daher braucht `UserDevice` eine zusätzliche Spalte `mdmId`, die die NanoMDM-UDID speichert.

## Architektur

```
Magic App (Tauri/Nuxt)
  │
  ├─ GET /api/magic/devices/:deviceId/mdm  →  Backend
  │                                          │
  │                                          ├─ Prisma: UserDevice.mdmId lesen
  │                                          │
  │                                          └─ pg.Pool → NanoMDM DB auf rebreak-mdm
  │                                              SELECT devices / command_results
  │
  └─ POST /api/magic/devices/:deviceId/mdm-link
       (setzt UserDevice.mdmId, z.B. nach USB-Enrollment)
```

## Datenmodell

```prisma
model UserDevice {
  // ... bestehende Felder ...
  mdmId String? @map("mdm_id")  // NanoMDM-UDID, nullable
}

model DeviceProtectionState {
    id             String    @id @default(uuid()) @db.Uuid
    userId         String    @map("user_id") @db.Uuid
    deviceId       String    @map("device_id")
    platform       String
    protectionType String    @map("protection_type")
    active         Boolean
    lastSeenAt     DateTime? @map("last_seen_at")
    changedAt      DateTime  @default(now()) @map("changed_at")
    reason         String?

    @@unique([userId, deviceId, protectionType])
    @@map("device_protection_states")
    @@schema("rebreak")
}

model DeviceProtectionStateLog {
    id             String   @id @default(uuid()) @db.Uuid
    userId         String   @map("user_id") @db.Uuid
    deviceId       String   @map("device_id")
    protectionType String   @map("protection_type")
    active         Boolean
    occurredAt     DateTime @map("occurred_at")
    reason         String?
    source         String

    @@index([userId, deviceId])
    @@map("device_protection_state_logs")
    @@schema("rebreak")
}
```

## Endpunkte

### `GET /api/magic/devices/:deviceId/mdm`
Auth: Magic-Session (`requireUser`)

Response enrolled:
```json
{
  "success": true,
  "data": {
    "enrolled": true,
    "company": "ReBreak",
    "supervised": true,
    "lockProfileInstalled": true,
    "lastAppPushAt": "2026-06-11T19:09:04.363Z"
  }
}
```

Response not enrolled:
```json
{ "success": true, "data": { "enrolled": false } }
```

Wenn `mdmId` gesetzt ist, aber NanoMDM das Gerät nicht mehr kennt, wird `mdmId` automatisch auf `null` gesetzt.

`lockProfileInstalled` wird nicht aus der bloßen MDM-Enrollment abgeleitet, sondern aus dem lokal gespeicherten `DeviceProtectionState` mit `protectionType = "nefilter"`. Solange die Native-App diesen Zustand noch nicht meldet, ist der Wert `false`.

### `POST /api/magic/devices/:deviceId/mdm-link`
Body: `{ "mdmId": "00008150-001C686601F0401C" }`

Setzt `UserDevice.mdmId` für das iOS-Gerät des aktuellen Users.

### `POST /api/devices/protection-state`
Body:
```json
{
  "deviceId": "string",
  "platform": "ios",
  "protectionType": "nefilter | vpn | dns",
  "active": true,
  "reason": "optional",
  "source": "optional"
}
```

Upsertet den per-Gerät-Schutz-Status und schreibt bei Änderung einen Log-Eintrag.

## Magic-App UI

`IosDeviceCard.vue` ruft über `useMdmStatus(deviceId)` den Backend-Status ab:

- **Enrolled**: Grüne Box mit Company / Supervised / Lock-Profil / Letzter App-Push.
- **Not enrolled**: Gelbe Box mit Hinweis: „Nicht MDM-enrolled. Verbinde das iPhone per USB. Das Enrollment dauert ca. 2 Minuten und geht ohne Datenverlust.“
- Wenn ein iPhone per USB verbunden ist, erscheint ein Button „Mit MDM verknüpfen“, der `linkMdmDevice(deviceId, udid)` aufruft.

## Infra / Env

- `MDM_DATABASE_URL` muss im Backend-Env gesetzt sein.
- `rebreak-mdm` PostgreSQL muss auf der öffentlichen IP lauschen und den Backend-Server in `pg_hba.conf` + UFW erlauben.

## Dateien

Backend:
- `backend/prisma/schema.prisma`
- `backend/nitro.config.ts`
- `backend/server/db/mdm.ts`
- `backend/server/db/device-protection.ts`
- `backend/server/api/magic/devices/[deviceId]/mdm.get.ts`
- `backend/server/api/magic/devices/[deviceId]/mdm-link.post.ts`
- `backend/server/api/devices/protection-state.post.ts`
- `backend/start-staging.sh`

Magic App:
- `apps/rebreak-magic/src-tauri/src/backend/api.rs`
- `apps/rebreak-magic/src-tauri/src/lib.rs`
- `apps/rebreak-magic/app/composables/useTauri.ts`
- `apps/rebreak-magic/app/composables/useMdmStatus.ts`
- `apps/rebreak-magic/app/components/IosDeviceCard.vue`
- `apps/rebreak-magic/app/components/DevLogDrawer.vue`

## Test-Ergebnis (Staging)

- Backend-Build: ✅
- Magic Rust: `cargo check` ✅
- Magic Nuxt-Build: ✅
- API-Test für User `charioanouar@gmail.com`, iPhone `MHFLX23QM0`:
  - `GET .../mdm` → `{ enrolled: true, company: "ReBreak", supervised: true, lockProfileInstalled: false, lastAppPushAt: "2026-06-11T19:09:04.363Z" }`
  - `mdmId` gelöscht → `{ enrolled: false }`
  - `POST .../mdm-link` → `{ mdmId: "00008150-001C686601F0401C" }`

## Offene TODOs

- `nefilter`, `vpn`, `dns` werden noch nicht von den Clients gemeldet. Dafür ist `POST /api/devices/protection-state` vorbereitet.
- Die Tabellen `device_protection_states` / `device_protection_state_logs` wurden für den schnellen Test manuell angelegt. Für Produktion muss eine Prisma-Migration erstellt und deployed werden.
- Die Spalte `mdm_id` wurde für den schnellen Test manuell mit `ALTER TABLE` angelegt.
