# Mobile Push Setup — APNs (iOS) + FCM (Android)

ONOXSOFT panel Faz F: native mobile push altyapısı.

Web Push (Faz E, browser push) ile birlikte çalışır.
`UnifiedNotificationService::notify($user, $payload)` çağrısı her iki kanala da gönderir.

---

## 1. Mimari

```
HTTP/Job → UnifiedNotificationService
              ├─ WebPushService     (browser, minishlink/web-push, VAPID)
              └─ MobilePushService  (iOS APNs + Android FCM)
                       │
                       ├─ APNs HTTP/2  (edamov/pushok)   — sandbox/production
                       └─ FCM v1 OAuth (kreait/firebase-php)
```

Tablo: `mobile_push_tokens` (model: `App\Domain\Notification\Models\MobileDevice`).
Tekil constraint: `(user_id, token)` — aynı kullanıcı aynı tokeni iki kez yazmaz.
Token rotate ederse `updateOrCreate` ile güncellenir.

**Soft-disable:** APNs/FCM "Unregistered" / "BadDeviceToken" dönerse `enabled = false`
(silmek yerine) — kullanıcı uygulamayı tekrar açıp register edince reactivate olur.

---

## 2. SDK Kurulumu (opsiyonel)

Paketler `composer.json` içinde `suggest` olarak listelendi.
Production'a geçerken iki komut:

```bash
composer require edamov/pushok        # iOS APNs HTTP/2
composer require kreait/firebase-php  # Android FCM v1
```

**Önemli:** SDK kurulu olmasa bile sistem çalışır. `class_exists` fallback ile
sessizce `Log::info` yazar (mesaj kaybolmaz, ama gerçek gönderim yapılmaz).

---

## 3. iOS APNs Yapılandırma

### 3.1 Apple Developer'dan Auth Key (.p8)

1. https://developer.apple.com/account/resources/authkeys/list
2. **Keys** → **+** → "Apple Push Notifications service (APNs)" işaretle
3. İsim ver (örn. "ONOXSOFT Panel APNs") → **Continue** → **Register**
4. **Download** — bu key sadece bir kere indirilebilir; kaybolursa yeni oluştur.
5. Sayfada görünen 10 karakterlik **KEY_ID** değerini not al.
6. Sol üstte **TEAM_ID** (10 karakter, hesap sayfasında).
7. App'in **BUNDLE_ID**'si: Xcode → target → General → Bundle Identifier.

### 3.2 Sunucu konfigürasyonu

`.p8` dosyasını sunucuya kopyala:

```bash
mkdir -p storage/app/apns
cp ~/Downloads/AuthKey_AB12CD34EF.p8 storage/app/apns/AuthKey.p8
chmod 600 storage/app/apns/AuthKey.p8
```

`.env`:

```env
APNS_KEY_ID=AB12CD34EF
APNS_TEAM_ID=1A2B3C4D5E
APNS_APP_BUNDLE_ID=com.onoxsoft.panel
APNS_PRIVATE_KEY_PATH=/var/www/onoxsoft/storage/app/apns/AuthKey.p8
APNS_PRODUCTION=false   # TestFlight/sandbox için false, App Store için true
```

---

## 4. Android FCM Yapılandırma

### 4.1 Firebase Console'dan Service Account

1. https://console.firebase.google.com → projeni seç (yoksa oluştur).
2. **Project Settings** (dişli) → **Service accounts** sekmesi
3. **Generate new private key** → `service-account.json` indir.
4. **Project ID** değerini sayfanın üstünden al (örn. `onoxsoft-panel`).

### 4.2 Sunucu konfigürasyonu

```bash
mkdir -p storage/app/fcm
cp ~/Downloads/onoxsoft-panel-firebase-adminsdk.json storage/app/fcm/service-account.json
chmod 600 storage/app/fcm/service-account.json
```

`.env`:

```env
FCM_PROJECT_ID=onoxsoft-panel
FCM_SERVICE_ACCOUNT_PATH=/var/www/onoxsoft/storage/app/fcm/service-account.json
```

### 4.3 Android client (Capacitor / Flutter / Native)

Firebase Console → **Add app** → Android → paket adını (`com.onoxsoft.panel`) gir →
`google-services.json` indir → Android projesine ekle.

---

## 5. Mobile Client Entegrasyonu

### 5.1 API Endpoint'leri (V1 — Sanctum bearer)

| Method | Path | Ne yapar? |
|--------|------|-----------|
| `POST`   | `/api/v1/mobile/devices`             | Token register / update (idempotent) |
| `GET`    | `/api/v1/mobile/devices`             | Kullanıcının cihaz listesi |
| `DELETE` | `/api/v1/mobile/devices/{token}`     | Token unregister (logout / opt-out) |
| `POST`   | `/api/v1/mobile/devices/test-push`   | Kullanıcının kendi cihazına test push |

Yetki: `auth:sanctum` bearer token (web login → personal access token).

### 5.2 Register payload örneği

```http
POST /api/v1/mobile/devices
Authorization: Bearer 1|abcdef…
Content-Type: application/json

{
  "platform": "ios",
  "token": "fef9d6a0c1f2…512-char-APNS-token",
  "device_id": "9F2B7CD4-1234-5678-90AB-CDEF0123456",
  "device_name": "Ahmet'in iPhone 15 Pro",
  "app_version": "1.0.3",
  "os_version": "17.5.1"
}
```

Response (201):

```json
{
  "data": {
    "id": 42,
    "platform": "ios",
    "device_name": "Ahmet'in iPhone 15 Pro",
    "enabled": true,
    "registered_at": "2026-05-15T10:23:00+00:00"
  }
}
```

### 5.3 iOS Swift örneği

```swift
import UserNotifications

UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, _ in
    if granted {
        DispatchQueue.main.async { UIApplication.shared.registerForRemoteNotifications() }
    }
}

// AppDelegate
func application(_ app: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    let token = deviceToken.map { String(format: "%02x", $0) }.joined()
    OnoxApi.registerPushToken(platform: "ios", token: token,
        deviceId: UIDevice.current.identifierForVendor?.uuidString,
        deviceName: UIDevice.current.name,
        appVersion: Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String,
        osVersion: UIDevice.current.systemVersion)
}
```

### 5.4 Android Kotlin örneği

```kotlin
FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
    if (task.isSuccessful) {
        val token = task.result
        api.registerPushToken(
            platform = "android",
            token = token,
            deviceId = Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID),
            deviceName = "${Build.MANUFACTURER} ${Build.MODEL}",
            appVersion = BuildConfig.VERSION_NAME,
            osVersion = "Android ${Build.VERSION.RELEASE}"
        )
    }
}

// FirebaseMessagingService.onNewToken — token rotate edilince
override fun onNewToken(token: String) = api.registerPushToken(...)
```

---

## 6. Backend'den Push Gönderme

```php
use App\Domain\Notification\Services\MobilePushService;
use App\Domain\Notification\Services\UnifiedNotificationService;

// Yalnız mobile
$result = app(MobilePushService::class)->send($user, [
    'title' => 'Hesabınız onaylandı',
    'body' => 'panel.onox.com.tr üzerinden giriş yapabilirsiniz.',
    'data' => ['type' => 'account_approved', 'account_id' => 123],
]);
// → ['sent' => 2, 'failed' => 0, 'invalid' => 0, 'errors' => []]

// Web + mobile birlikte
$result = app(UnifiedNotificationService::class)->notify($user, [...]);
// → ['web' => [...], 'mobile' => [...]]

// Role broadcast
app(UnifiedNotificationService::class)->broadcastToRole('admin', [
    'title' => 'Kritik sistem alarmı',
    'body' => 'mysqld servisi durdu',
]);
```

---

## 7. Admin Paneli

`/admin/notifications/mobile` — config durumu, cihaz istatistikleri, test push butonu.

Test push butonu admin'in kendi cihazlarına gönderir; SDK kurulu olmasa bile çalışır
(sonuç `sent: 0` ama `storage/logs/laravel.log` içinde mesaj görünür).

---

## 8. Troubleshooting

| Belirti | Sebep | Çözüm |
|---------|-------|-------|
| `reason: web_push_package_missing` | minishlink/web-push yok | `composer install` |
| `reason: no_devices` | Kullanıcı henüz token register etmedi | Mobile app'i aç, izin ver |
| APNs `BadDeviceToken` | Sandbox/production karışıklığı | `.env` `APNS_PRODUCTION` doğru ayarla |
| APNs `Unregistered` | Kullanıcı app'i sildi | Sistem otomatik soft-disable yapar |
| FCM `UNREGISTERED` | Token expired / app uninstall | Sistem otomatik soft-disable yapar |
| FCM `INVALID_ARGUMENT` | Service account JSON yanlış proje | Doğru projeden indir |

---

## 9. Dosya Referansları

- Service: `app/Domain/Notification/Services/MobilePushService.php`
- Unified: `app/Domain/Notification/Services/UnifiedNotificationService.php`
- Model: `app/Domain/Notification/Models/MobileDevice.php`
- Exception: `app/Domain/Notification/Exceptions/DeviceTokenInvalidException.php`
- API Controller: `app/Http/Controllers/Api/V1/MobileDeviceController.php`
- Admin Controller: `app/Http/Controllers/Admin/MobilePushController.php`
- Migration: `database/migrations/2026_05_15_140000_extend_mobile_push_tokens_to_devices.php`
- Config: `config/services.php` → `apns` + `fcm` anahtarları
- Vue: `resources/js/pages/Admin/Notifications/Mobile.vue`
- DI binding: `app/Providers/AppServiceProvider.php`
