Update: 2026-05-06 04:55:22
This commit is contained in:
143
app/Services/NotificationService.php
Normal file
143
app/Services/NotificationService.php
Normal file
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
/**
|
||||
* Firebase Notification Service (FCM HTTP v1)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Core\Database;
|
||||
use App\Core\Security;
|
||||
|
||||
class NotificationService
|
||||
{
|
||||
private string $projectId;
|
||||
private string $serviceAccountPath;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->projectId = env('FIREBASE_PROJECT_ID', '');
|
||||
$this->serviceAccountPath = env('FIREBASE_SERVICE_ACCOUNT_PATH', APP_PATH . '/config/firebase-service-account.json');
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a push notification to a specific user or device
|
||||
*/
|
||||
public function sendNotification(string $userId, string $title, string $body, array $data = [], ?string $deviceId = null): bool
|
||||
{
|
||||
$db = Database::getInstance();
|
||||
|
||||
// 1. Get push tokens for the user
|
||||
if ($deviceId) {
|
||||
$stmt = $db->prepare("SELECT push_token FROM user_devices WHERE user_id = ? AND device_fingerprint = ? AND push_token IS NOT NULL");
|
||||
$stmt->execute([$userId, $deviceId]);
|
||||
} else {
|
||||
$stmt = $db->prepare("SELECT push_token FROM user_devices WHERE user_id = ? AND push_token IS NOT NULL AND is_active = 1");
|
||||
$stmt->execute([$userId]);
|
||||
}
|
||||
|
||||
$tokens = $stmt->fetchAll(\PDO::FETCH_COLUMN);
|
||||
|
||||
if (empty($tokens)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. Save notification to database (Single direct insert)
|
||||
$stmt = $db->prepare("SELECT tenant_id FROM users WHERE id = ? LIMIT 1");
|
||||
$stmt->execute([$userId]);
|
||||
$tenantId = $stmt->fetchColumn();
|
||||
|
||||
if ($tenantId) {
|
||||
$stmt = $db->prepare("INSERT INTO notifications (id, tenant_id, user_id, type, title, body, data, created_at) VALUES (UUID(), ?, ?, 'system', ?, ?, ?, NOW())");
|
||||
$stmt->execute([$tenantId, $userId, $title, $body, json_encode($data)]);
|
||||
}
|
||||
|
||||
// 3. Send to each token
|
||||
$successCount = 0;
|
||||
foreach ($tokens as $token) {
|
||||
if ($this->dispatchToFcm($token, $title, $body, $data)) {
|
||||
$successCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return $successCount > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch notification to Firebase via HTTP v1 API
|
||||
*/
|
||||
private function dispatchToFcm(string $token, string $title, string $body, array $data): bool
|
||||
{
|
||||
if (!file_exists($this->serviceAccountPath)) {
|
||||
error_log("[NotificationService] Firebase service account file missing: {$this->serviceAccountPath}");
|
||||
return false;
|
||||
}
|
||||
|
||||
$accessToken = $this->getAccessToken();
|
||||
if (!$accessToken) return false;
|
||||
|
||||
$url = "https://fcm.googleapis.com/v1/projects/{$this->projectId}/messages:send";
|
||||
|
||||
$payload = [
|
||||
'message' => [
|
||||
'token' => $token,
|
||||
'notification' => [
|
||||
'title' => $title,
|
||||
'body' => $body,
|
||||
],
|
||||
'data' => array_map('strval', $data), // FCM data values must be strings
|
||||
'android' => [
|
||||
'priority' => 'high',
|
||||
'notification' => [
|
||||
'sound' => 'default',
|
||||
'channel_id' => 'high_importance_channel'
|
||||
]
|
||||
],
|
||||
'apns' => [
|
||||
'payload' => [
|
||||
'aps' => [
|
||||
'sound' => 'default',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Authorization: Bearer ' . $accessToken,
|
||||
'Content-Type: application/json',
|
||||
]);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($httpCode !== 200) {
|
||||
error_log("[NotificationService] FCM Send Error ($httpCode): " . $response);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get OAuth2 Access Token for Firebase (Cache this in production!)
|
||||
* Note: This requires a JWT library or manual signing.
|
||||
* For simplicity, we assume the user might use a Google Auth library.
|
||||
* But since we avoid extra deps, I will provide a minimal implementation or suggestion.
|
||||
*/
|
||||
private function getAccessToken(): ?string
|
||||
{
|
||||
// This is a complex part that usually requires 'google/auth' library.
|
||||
// For now, I will return null and tell the user they need to install google/auth via composer
|
||||
// OR I can write a minimal JWT signer for Google Auth if they don't want composer.
|
||||
error_log("[NotificationService] OAuth2 Token generation needs google/auth library.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user