push notification api
This commit is contained in:
parent
8b8335eb32
commit
045b8d1f4c
@ -296,6 +296,33 @@ public function broadcastNotification(Request $request)
|
|||||||
'updated_at' => now(),
|
'updated_at' => now(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Send FCM push notifications for broadcast
|
||||||
|
if ($request->channel === 'push' || $request->channel === 'both') {
|
||||||
|
$recipientsLower = strtolower($request->recipients);
|
||||||
|
if (str_contains($recipientsLower, 'worker') || $recipientsLower === 'all') {
|
||||||
|
$workers = \App\Models\Worker::whereNotNull('fcm_token')->get();
|
||||||
|
foreach ($workers as $worker) {
|
||||||
|
\App\Services\FCMService::sendPushNotification(
|
||||||
|
$worker->fcm_token,
|
||||||
|
$request->title,
|
||||||
|
$request->message,
|
||||||
|
['type' => 'broadcast']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (str_contains($recipientsLower, 'employer') || $recipientsLower === 'all') {
|
||||||
|
$employers = \App\Models\User::where('role', 'employer')->whereNotNull('fcm_token')->get();
|
||||||
|
foreach ($employers as $employer) {
|
||||||
|
\App\Services\FCMService::sendPushNotification(
|
||||||
|
$employer->fcm_token,
|
||||||
|
$request->title,
|
||||||
|
$request->message,
|
||||||
|
['type' => 'broadcast']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return back()->with('success', "Notification campaign broadcasted successfully to all {$request->recipients} users!");
|
return back()->with('success', "Notification campaign broadcasted successfully to all {$request->recipients} users!");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -523,13 +550,15 @@ public function storeAnnouncement(Request $request)
|
|||||||
'content' => $request->content,
|
'content' => $request->content,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
\App\Models\Announcement::create([
|
$ann = \App\Models\Announcement::create([
|
||||||
'title' => $request->title,
|
'title' => $request->title,
|
||||||
'body' => $body,
|
'body' => $body,
|
||||||
'type' => 'Charity',
|
'type' => 'Charity',
|
||||||
'status' => 'approved',
|
'status' => 'approved',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$this->notifyUsersOfEvent($ann);
|
||||||
|
|
||||||
return back()->with('success', 'Charity Event created successfully.');
|
return back()->with('success', 'Charity Event created successfully.');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -541,6 +570,8 @@ public function approveAnnouncement(Request $request, $id)
|
|||||||
$ann = \App\Models\Announcement::findOrFail($id);
|
$ann = \App\Models\Announcement::findOrFail($id);
|
||||||
$ann->update(['status' => 'approved']);
|
$ann->update(['status' => 'approved']);
|
||||||
|
|
||||||
|
$this->notifyUsersOfEvent($ann);
|
||||||
|
|
||||||
return back()->with('success', 'Charity Event approved successfully.');
|
return back()->with('success', 'Charity Event approved successfully.');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -572,4 +603,48 @@ public function deleteAnnouncement(Request $request, $id)
|
|||||||
|
|
||||||
return back()->with('success', 'Charity Event deleted successfully.');
|
return back()->with('success', 'Charity Event deleted successfully.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcast FCM Push Notification for new Event/Announcement.
|
||||||
|
*/
|
||||||
|
protected function notifyUsersOfEvent($ann)
|
||||||
|
{
|
||||||
|
$title = "New Event: " . $ann->title;
|
||||||
|
$body = $ann->body;
|
||||||
|
|
||||||
|
if (str_starts_with($ann->body, '{"type":"Charity"')) {
|
||||||
|
$decoded = json_decode($ann->body, true);
|
||||||
|
if ($decoded && isset($decoded['content'])) {
|
||||||
|
$body = $decoded['content'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify all workers
|
||||||
|
$workers = \App\Models\Worker::whereNotNull('fcm_token')->get();
|
||||||
|
foreach ($workers as $worker) {
|
||||||
|
\App\Services\FCMService::sendPushNotification(
|
||||||
|
$worker->fcm_token,
|
||||||
|
$title,
|
||||||
|
$body,
|
||||||
|
[
|
||||||
|
'type' => 'announcement',
|
||||||
|
'announcement_id' => $ann->id,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify all employers
|
||||||
|
$employers = \App\Models\User::where('role', 'employer')->whereNotNull('fcm_token')->get();
|
||||||
|
foreach ($employers as $employer) {
|
||||||
|
\App\Services\FCMService::sendPushNotification(
|
||||||
|
$employer->fcm_token,
|
||||||
|
$title,
|
||||||
|
$body,
|
||||||
|
[
|
||||||
|
'type' => 'announcement',
|
||||||
|
'announcement_id' => $ann->id,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,6 +25,7 @@ public function login(Request $request)
|
|||||||
'email' => 'nullable|string',
|
'email' => 'nullable|string',
|
||||||
'mobile' => 'nullable|string',
|
'mobile' => 'nullable|string',
|
||||||
'password' => 'required|string',
|
'password' => 'required|string',
|
||||||
|
'fcm_token' => 'nullable|string|max:255',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$validator->after(function ($validator) use ($request) {
|
$validator->after(function ($validator) use ($request) {
|
||||||
@ -103,7 +104,11 @@ public function login(Request $request)
|
|||||||
], 403);
|
], 403);
|
||||||
}
|
}
|
||||||
|
|
||||||
$user->update(['api_token' => $apiToken]);
|
$userUpdateData = ['api_token' => $apiToken];
|
||||||
|
if ($request->has('fcm_token')) {
|
||||||
|
$userUpdateData['fcm_token'] = $request->fcm_token;
|
||||||
|
}
|
||||||
|
$user->update($userUpdateData);
|
||||||
if ($sponsor) {
|
if ($sponsor) {
|
||||||
$sponsor->update([
|
$sponsor->update([
|
||||||
'api_token' => $apiToken,
|
'api_token' => $apiToken,
|
||||||
|
|||||||
@ -93,6 +93,7 @@ public function updateProfile(Request $request)
|
|||||||
'notifications' => 'required|boolean',
|
'notifications' => 'required|boolean',
|
||||||
'current_password' => 'nullable|required_with:new_password|string',
|
'current_password' => 'nullable|required_with:new_password|string',
|
||||||
'new_password' => 'nullable|string|min:8|confirmed',
|
'new_password' => 'nullable|string|min:8|confirmed',
|
||||||
|
'fcm_token' => 'nullable|string|max:255',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if ($validator->fails()) {
|
if ($validator->fails()) {
|
||||||
@ -121,10 +122,14 @@ public function updateProfile(Request $request)
|
|||||||
$oldEmail = $employer->getOriginal('email') ?? $employer->email;
|
$oldEmail = $employer->getOriginal('email') ?? $employer->email;
|
||||||
|
|
||||||
// Update user table
|
// Update user table
|
||||||
$employer->update([
|
$userData = [
|
||||||
'name' => $request->name,
|
'name' => $request->name,
|
||||||
'email' => $request->email,
|
'email' => $request->email,
|
||||||
]);
|
];
|
||||||
|
if ($request->has('fcm_token')) {
|
||||||
|
$userData['fcm_token'] = $request->fcm_token;
|
||||||
|
}
|
||||||
|
$employer->update($userData);
|
||||||
|
|
||||||
// Sync with corresponding sponsor record if found
|
// Sync with corresponding sponsor record if found
|
||||||
$matchingSponsor = \App\Models\Sponsor::where('email', $oldEmail)->first();
|
$matchingSponsor = \App\Models\Sponsor::where('email', $oldEmail)->first();
|
||||||
|
|||||||
@ -798,6 +798,19 @@ public function hireCandidate(Request $request, $id = null)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dispatch push notification to worker
|
||||||
|
if ($worker && $worker->fcm_token) {
|
||||||
|
\App\Services\FCMService::sendPushNotification(
|
||||||
|
$worker->fcm_token,
|
||||||
|
"Congratulations! You've been Hired",
|
||||||
|
"Employer " . ($employer->name ?? "Employer") . " has hired you.",
|
||||||
|
[
|
||||||
|
'type' => 'hired',
|
||||||
|
'employer_id' => $employer->id,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'message' => 'Candidate successfully marked as Hired!',
|
'message' => 'Candidate successfully marked as Hired!',
|
||||||
|
|||||||
@ -441,6 +441,7 @@ public function login(Request $request)
|
|||||||
'phone' => 'required_without:email|nullable|string',
|
'phone' => 'required_without:email|nullable|string',
|
||||||
'email' => 'required_without:phone|nullable|email',
|
'email' => 'required_without:phone|nullable|email',
|
||||||
'password' => 'required|string',
|
'password' => 'required|string',
|
||||||
|
'fcm_token' => 'nullable|string|max:255',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if ($validator->fails()) {
|
if ($validator->fails()) {
|
||||||
@ -464,7 +465,11 @@ public function login(Request $request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
$apiToken = Str::random(80);
|
$apiToken = Str::random(80);
|
||||||
$worker->update(['api_token' => $apiToken]);
|
$updateData = ['api_token' => $apiToken];
|
||||||
|
if ($request->has('fcm_token')) {
|
||||||
|
$updateData['fcm_token'] = $request->fcm_token;
|
||||||
|
}
|
||||||
|
$worker->update($updateData);
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
|
|||||||
@ -271,6 +271,21 @@ public function sendMessage(Request $request, $id)
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Dispatch push notification to employer
|
||||||
|
$employer = $conv->employer;
|
||||||
|
if ($employer && $employer->fcm_token) {
|
||||||
|
\App\Services\FCMService::sendPushNotification(
|
||||||
|
$employer->fcm_token,
|
||||||
|
"New Message from " . ($worker->name ?? "Worker"),
|
||||||
|
$request->text ?: "Sent an attachment",
|
||||||
|
[
|
||||||
|
'conversation_id' => $conv->id,
|
||||||
|
'sender_type' => 'worker',
|
||||||
|
'sender_id' => $worker->id,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'message' => 'Message sent successfully.',
|
'message' => 'Message sent successfully.',
|
||||||
|
|||||||
@ -351,6 +351,22 @@ public function respondToOffer(Request $request, $id)
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Dispatch push notification to employer
|
||||||
|
$employer = $offer->employer;
|
||||||
|
if ($employer && $employer->fcm_token) {
|
||||||
|
$statusMessage = $responseStatus === 'accepted' ? 'accepted' : 'rejected';
|
||||||
|
\App\Services\FCMService::sendPushNotification(
|
||||||
|
$employer->fcm_token,
|
||||||
|
"Job Offer " . ucfirst($statusMessage),
|
||||||
|
"Worker " . ($worker->name ?? "Candidate") . " has " . $statusMessage . " your job offer.",
|
||||||
|
[
|
||||||
|
'type' => 'job_offer_response',
|
||||||
|
'offer_id' => $offer->id,
|
||||||
|
'status' => $responseStatus,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'message' => "Job offer successfully " . $responseStatus . ".",
|
'message' => "Job offer successfully " . $responseStatus . ".",
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
|
|
||||||
#[Fillable(['name', 'email', 'password', 'role', 'status', 'subscription_status', 'subscription_expires_at', 'api_token'])]
|
#[Fillable(['name', 'email', 'password', 'role', 'status', 'subscription_status', 'subscription_expires_at', 'api_token', 'fcm_token'])]
|
||||||
#[Hidden(['password', 'remember_token', 'api_token'])]
|
#[Hidden(['password', 'remember_token', 'api_token'])]
|
||||||
class User extends Authenticatable
|
class User extends Authenticatable
|
||||||
{
|
{
|
||||||
|
|||||||
@ -119,6 +119,20 @@ public static function sendPushNotification($token, $title, $body, array $data =
|
|||||||
'title' => $title,
|
'title' => $title,
|
||||||
'body' => $body,
|
'body' => $body,
|
||||||
],
|
],
|
||||||
|
'android' => [
|
||||||
|
'notification' => [
|
||||||
|
'click_action' => 'FLUTTER_NOTIFICATION_CLICK',
|
||||||
|
'sound' => 'default',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'apns' => [
|
||||||
|
'payload' => [
|
||||||
|
'aps' => [
|
||||||
|
'sound' => 'default',
|
||||||
|
'badge' => 1,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->string('fcm_token')->nullable()->after('api_token');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('fcm_token');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -202,4 +202,40 @@ public function test_unauthenticated_requests_are_blocked()
|
|||||||
$response = $this->getJson('/api/workers/conversations');
|
$response = $this->getJson('/api/workers/conversations');
|
||||||
$response->assertStatus(401);
|
$response->assertStatus(401);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test worker message triggers push notification.
|
||||||
|
*/
|
||||||
|
public function test_worker_message_triggers_push_notification()
|
||||||
|
{
|
||||||
|
\Illuminate\Support\Facades\Http::fake([
|
||||||
|
'https://oauth2.googleapis.com/token' => \Illuminate\Support\Facades\Http::response(['access_token' => 'mocked-fcm-token'], 200),
|
||||||
|
'https://fcm.googleapis.com/*' => \Illuminate\Support\Facades\Http::response(['name' => 'projects/migrant-fe5a7/messages/mock-id'], 200),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Set fcm_token on employer
|
||||||
|
$this->employer->update(['fcm_token' => 'employer-device-fcm-token-123']);
|
||||||
|
|
||||||
|
// Create conversation
|
||||||
|
$conversation = Conversation::create([
|
||||||
|
'employer_id' => $this->employer->id,
|
||||||
|
'worker_id' => $this->worker->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Reply request
|
||||||
|
$response = $this->withHeaders([
|
||||||
|
'Authorization' => 'Bearer ' . $this->token,
|
||||||
|
])->postJson('/api/workers/conversations/' . $conversation->id . '/messages', [
|
||||||
|
'text' => 'Testing push notification to employer!'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response->assertStatus(201);
|
||||||
|
|
||||||
|
// Assert Http call was made to FCM endpoint
|
||||||
|
\Illuminate\Support\Facades\Http::assertSent(function ($request) {
|
||||||
|
return str_contains($request->url(), 'fcm.googleapis.com') &&
|
||||||
|
$request['message']['token'] === 'employer-device-fcm-token-123' &&
|
||||||
|
$request['message']['notification']['body'] === 'Testing push notification to employer!';
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user