From 151248d448d2219ac0aaad4666391487c9b8f69e Mon Sep 17 00:00:00 2001 From: Linksehat Staging Server Date: Tue, 28 May 2024 09:00:27 +0700 Subject: [PATCH] update bugs user notification --- .../Http/Controllers/Api/AuthController.php | 16 +- .../Api/Doctor/AuthDoctorController.php | 17 +- .../Api/Doctor/ChatDoctorController.php | 9 +- .../Controllers/Api/LivechatController.php | 47 +++-- app/Models/OLDLMS/User.php | 5 +- app/Models/User.php | 11 +- app/Notifications/SendNotification.php | 67 +++++- config/firebase.php | 192 ++++++++++++++++++ ...83926_create_notification_tokens_table.php | 3 +- ..._03_143801_add_column_to_notifications.php | 32 +++ 10 files changed, 353 insertions(+), 46 deletions(-) create mode 100644 config/firebase.php create mode 100644 database/migrations/2024_01_03_143801_add_column_to_notifications.php diff --git a/Modules/Linksehat/Http/Controllers/Api/AuthController.php b/Modules/Linksehat/Http/Controllers/Api/AuthController.php index b8c83ec7..543897f8 100644 --- a/Modules/Linksehat/Http/Controllers/Api/AuthController.php +++ b/Modules/Linksehat/Http/Controllers/Api/AuthController.php @@ -48,8 +48,18 @@ class AuthController extends Controller $user = User::with('detail') ->where('sEmail', $request->phone_or_email) ->first(); - $user->fcm_token = $request->fcm_token; - $user->save(); + + $user->notificationTokens()->updateOrCreate([ + 'device_id' => $request->device_id, + 'token' => $request->fcm_token, + ], [ + 'origin' => $request->origin, + 'device_id' => $request->device_id, + 'type' => $request->type, + 'token' => $request->fcm_token, + 'status' => $request->status, + ]); + return Helper::responseJson( data: [ 'token' => $user->createToken('app')->plainTextToken, @@ -134,7 +144,7 @@ class AuthController extends Controller ]); $user->fcm_token = $request->fcm_token; $user->save(); - + return Helper::responseJson( data: [ 'token' => $user->createToken('app')->plainTextToken, diff --git a/Modules/Linksehat/Http/Controllers/Api/Doctor/AuthDoctorController.php b/Modules/Linksehat/Http/Controllers/Api/Doctor/AuthDoctorController.php index 12e61b53..14c36b91 100644 --- a/Modules/Linksehat/Http/Controllers/Api/Doctor/AuthDoctorController.php +++ b/Modules/Linksehat/Http/Controllers/Api/Doctor/AuthDoctorController.php @@ -25,11 +25,11 @@ class AuthDoctorController extends Controller $data = [ 'email' => $request->email, 'password' => $request->password, - 'fcm_token' => $request->fcm_token + 'fcm_token' => $request->fcm_token, ]; $validator = Validator::make($request->all(), [ 'email' => 'required|email', - 'password' => 'required', + 'password' => 'required', 'fcm_token' => 'required' ], [ 'email.required' => trans('Validation.required',['attribute' => 'Email']), @@ -53,8 +53,17 @@ class AuthDoctorController extends Controller return ApiResponse::apiResponse('Bad Request', $data, trans('Message.password'), 400); } - $user->fcm_token = $request->fcm_token; - $user->save(); + $user->notificationTokens()->updateOrCreate([ + 'device_id' => $request->device_id, + 'token' => $request->fcm_token, + ], [ + 'origin' => $request->origin, + 'device_id' => $request->device_id, + 'type' => $request->type, + 'token' => $request->fcm_token, + 'status' => $request->status, + ]); + $res_data = [ // 'user' => $user, diff --git a/Modules/Linksehat/Http/Controllers/Api/Doctor/ChatDoctorController.php b/Modules/Linksehat/Http/Controllers/Api/Doctor/ChatDoctorController.php index 22d2d46c..7b4848db 100644 --- a/Modules/Linksehat/Http/Controllers/Api/Doctor/ChatDoctorController.php +++ b/Modules/Linksehat/Http/Controllers/Api/Doctor/ChatDoctorController.php @@ -24,6 +24,8 @@ use Modules\HospitalPortal\Helpers\ApiResponse; use App\Helpers\Helper; use Illuminate\Support\Facades\View; use Illuminate\Support\Facades\DB; +use Kreait\Firebase\Messaging\CloudMessage; +use Kreait\Laravel\Firebase\Facades\Firebase; class ChatDoctorController extends Controller { @@ -160,7 +162,7 @@ class ChatDoctorController extends Controller ); } - + return ApiResponse::apiResponse("Success",['message' => 'Livechat updated successfully'], trans('Message.success'), 200); } else { return response()->json(['message' => 'Livechat not found'], 404); @@ -192,7 +194,6 @@ class ChatDoctorController extends Controller ]; $user = UserLMS::where('nID',$livechat->patient_id)->first(); - if ($user) { $user->notify(new SendNotification($title, $body, $dataNotif)); return ApiResponse::apiResponse("Success",['message' => 'Livechat updated successfully'], trans('Message.success'), 200); @@ -203,7 +204,7 @@ class ChatDoctorController extends Controller message: 'Doctor not found.' ); } - + } else { return response()->json(['message' => 'Livechat not found'], 404); } @@ -234,7 +235,7 @@ class ChatDoctorController extends Controller ]; $user = UserLMS::where('nID',$livechat->patient_id)->first(); - + if ($user) { $user->notify(new SendNotification($title, $body, $dataNotif)); return ApiResponse::apiResponse("Success",['message' => 'Livechat updated successfully'], trans('Message.success'), 200); diff --git a/Modules/Linksehat/Http/Controllers/Api/LivechatController.php b/Modules/Linksehat/Http/Controllers/Api/LivechatController.php index d0ecdfa7..8a240e38 100644 --- a/Modules/Linksehat/Http/Controllers/Api/LivechatController.php +++ b/Modules/Linksehat/Http/Controllers/Api/LivechatController.php @@ -23,6 +23,9 @@ use Illuminate\Support\Facades\Validator; use App\Http\Controllers\DuitkuController; use DB; +use Illuminate\Contracts\Filesystem\Cloud; +use Kreait\Firebase\Messaging\CloudMessage; +use Kreait\Laravel\Firebase\Facades\Firebase; use Str; class LivechatController extends Controller @@ -48,7 +51,7 @@ class LivechatController extends Controller $urlAvatarDefault = $user->detail->nIDJenisKelamin == 1 ? 'https://linksehat.dev/assets/img/users/male-avatar.png' : 'https://linksehat.dev/assets/img/users/female-avatar.png'; $avatarMember = $user->detail->sImage ?? $urlAvatarDefault; $relationship = DB::connection('oldlms')->table('tm_hubungan_keluarga')->where('nID', $user->nIDHubunganKeluarga)->first('sHubunganKeluarga'); - + $dataUser = [ 'id' => $user->nID, 'name' => $user->sFirstName . ' ' . $user->sLastName, @@ -62,7 +65,7 @@ class LivechatController extends Controller $urlAvatarDefault = $m['detail']['nIDJenisKelamin'] == 1 ? 'https://linksehat.dev/assets/img/users/male-avatar.png' : 'https://linksehat.dev/assets/img/users/female-avatar.png'; $avatarMember = $m['detail']['sImage'] ?? $urlAvatarDefault; $relationship = DB::connection('oldlms')->table('tm_hubungan_keluarga')->where('nID', $m['nIDHubunganKeluarga'])->first('sHubunganKeluarga'); - + $data = [ 'id' => $m['nID'], 'name' => $m['full_name'], @@ -78,17 +81,17 @@ class LivechatController extends Controller $memberProfile = User::with('detail')->where('nIDUser', $nID)->get()->toArray(); $dataMember = User::with('detail')->where('nID', $nID)->get()->first(); - + if ($user->detail){ $urlAvatarDefault = $user->detail->nIDJenisKelamin == 1 ? 'https://linksehat.dev/assets/img/users/male-avatar.png' : 'https://linksehat.dev/assets/img/users/female-avatar.png'; } else { $urlAvatarDefault = 'https://linksehat.dev/assets/img/users/male-avatar.png'; } $avatar = $user->detail->sImage ?? $urlAvatarDefault; - + $avatarMember = $dataMember->detail->sImage ?? $urlAvatarDefault; $relationship = DB::connection('oldlms')->table('tm_hubungan_keluarga')->where('nID', $user->detail->nIDHubunganKeluarga)->first('sHubunganKeluarga'); - + $dataUser = [ 'id' => $dataMember->nID, 'name' => $dataMember->sFirstName . ' ' . $dataMember->sLastName, @@ -102,20 +105,20 @@ class LivechatController extends Controller $urlAvatarDefault = $m['detail']['nIDJenisKelamin'] == 1 ? 'https://linksehat.dev/assets/img/users/male-avatar.png' : 'https://linksehat.dev/assets/img/users/female-avatar.png'; $avatarMember = $m['detail']['sImage'] ?? $urlAvatarDefault; $relationship = DB::connection('oldlms')->table('tm_hubungan_keluarga')->where('nID', $m['nIDHubunganKeluarga'])->first('sHubunganKeluarga'); - + $data = [ 'id' => $m['nID'], 'name' => $m['full_name'], 'relationship' => $relationship->sHubunganKeluarga, 'avatar' => $avatarMember, ]; - + array_push( $dataMemberProfile, $data); } } - } + } } - + return Helper::responseJson([ 'member' => $dataMemberProfile ]); @@ -129,7 +132,7 @@ class LivechatController extends Controller 'organization_id' => $request->organization_id, 'descriptions' => $request->descriptions ]; - + $validator = Validator::make($request->all(), [ 'doctor_id' => 'required', 'patient_id' => 'required', @@ -139,7 +142,7 @@ class LivechatController extends Controller 'patient_id.required' => 'ID Pasien harus diisi', 'descriptions.required' => 'Deskripsi harus diisi', ]); - + if ($validator->fails()) { return Helper::responseJson( status: 'Bad Request', @@ -148,18 +151,18 @@ class LivechatController extends Controller ); } else { // insert table livechat - + /** * Status Livechat * 1=Request, 2=Accept, 3=Decline, 4=Waiting Payment, 5=Success Payment, 6 = End Chat, 7=Payment Failed */ - + $timezone = date_default_timezone_get(); $data['request_date'] = date('Y-m-d H:i:s'); $data['timezone'] = $timezone; $data['uuid'] = (string) Str::orderedUuid(); $data['status'] = 1; // Request - + $livechat = Livechat::create($data); $doctor = $livechat->doctor; $responseData = [ @@ -167,7 +170,7 @@ class LivechatController extends Controller 'request_date' => $livechat->request_date, 'image_path' => 'https' // Ganti dengan path yang benar jika ada ]; - + // Send Notification $doctorId = $livechat->doctor_id; $user = UserAso::find($doctorId); @@ -183,7 +186,7 @@ class LivechatController extends Controller 'livechat_id' => $livechat->id, 'type' => 'request-chat' ]; - + if ($user) { $user->notify(new SendNotification($title, $body, $dataNotif)); return Helper::responseJson(data: $responseData); @@ -195,8 +198,8 @@ class LivechatController extends Controller ); } } - } - + } + public function consultation_request_show($id){ $livechat = Livechat::where('id', $id)->with(['doctor', 'practitioner'])->first(); $practitionerRole = PractitionerRole::where('id',$livechat->practitioner->id)->first(); @@ -226,7 +229,7 @@ class LivechatController extends Controller ], ], 'total' => $totalPay - + ]; return Helper::responseJson(data: $data); } @@ -244,9 +247,9 @@ class LivechatController extends Controller 'active' => 1, 'config_pmc_id' => 2, ])->get()->toArray(); - + $payment = DuitkuHelper::paymentMethod(); - + $price = $practitionerRole->price ? $practitionerRole->price : 30000; $discount = 0; $adminFee = 5000; @@ -262,7 +265,7 @@ class LivechatController extends Controller 'va' => $va ] // 'payment_method' => json_decode($payment) - + ]; return Helper::responseJson(data: $data); } diff --git a/app/Models/OLDLMS/User.php b/app/Models/OLDLMS/User.php index f06d6da7..b940f65f 100644 --- a/app/Models/OLDLMS/User.php +++ b/app/Models/OLDLMS/User.php @@ -73,5 +73,8 @@ class User extends Authenticatable return $this->morphMany(NotificationToken::class, 'notifiabletoken'); } - + public function routeNotificationForFcm() + { + return $this->notificationTokens()->pluck('token')->toArray(); + } } diff --git a/app/Models/User.php b/app/Models/User.php index 64263dd5..f7dde5f6 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -86,7 +86,7 @@ class User extends Authenticatable { return $this->belongsToMany(Corporate::class, 'corporate_manager', 'user_id', 'corporate_id'); } - + public function metas() { return $this->morphMany(Meta::class, 'metaable'); @@ -102,13 +102,18 @@ class User extends Authenticatable return $this->hasMany(Person::class, 'owner_user_id'); } + public function getOrganization() + { + return $this->hasOne(OrganizationUser::class, 'user_id'); + } + public function notificationTokens() { return $this->morphMany(NotificationToken::class, 'notifiabletoken'); } - public function getOrganization() + public function routeNotificationForFcm() { - return $this->hasOne(OrganizationUser::class, 'user_id'); + return $this->notificationTokens()->pluck('token')->toArray(); } } diff --git a/app/Notifications/SendNotification.php b/app/Notifications/SendNotification.php index 4473a915..b2ebdbab 100644 --- a/app/Notifications/SendNotification.php +++ b/app/Notifications/SendNotification.php @@ -4,8 +4,17 @@ namespace App\Notifications; use Illuminate\Bus\Queueable; use Illuminate\Notifications\Notification; +use Kreait\Firebase\Messaging\CloudMessage; +use Kreait\Laravel\Firebase\Facades\Firebase; use NotificationChannels\Fcm\FcmChannel; -use NotificationChannels\Fcm\FcmMessage; +use NotificationChannels\Fcm\FcmMessage;; +use NotificationChannels\Fcm\Resources\AndroidConfig; +use NotificationChannels\Fcm\Resources\AndroidFcmOptions; +use NotificationChannels\Fcm\Resources\AndroidNotification; +use NotificationChannels\Fcm\Resources\ApnsConfig; +use NotificationChannels\Fcm\Resources\ApnsFcmOptions; +use NotificationChannels\Fcm\Resources\Notification as FcmNotification; + class SendNotification extends Notification { @@ -19,7 +28,7 @@ class SendNotification extends Notification { $this->title = $title; $this->body = $body; - $this->data = $data; + $this->data = is_array($data) ? $data : (array) $data; // Pastikan data adalah array } public function via($notifiable) @@ -27,13 +36,55 @@ class SendNotification extends Notification return [FcmChannel::class]; } + // public function toFcm($notifiable) + // { + // return FcmMessage::create() + // ->setData($this->data) + // ->setNotification([ + // 'title' => $this->title, + // 'body' => $this->body, + // ]); + // } + public function toFcm($notifiable) { - return FcmMessage::create() - ->setData($this->data) - ->setNotification([ - 'title' => $this->title, - 'body' => $this->body, - ]); + $deviceTokens = $notifiable->routeNotificationFor('fcm'); + $notification = [ + 'title' => $this->title, + 'body' => $this->body, + ]; + + if (count($deviceTokens)){ + foreach($deviceTokens as $token) { + $message = CloudMessage::withTarget('token', $token) + ->withNotification($notification) // optional + ->withData($this->data); + Firebase::messaging()->send($message); + } + } + + $dataFcm = FcmMessage::create() + ->setToken($deviceTokens[0]) + ->setData([]) + ->setNotification( + FcmNotification::create() + ->setTitle('ini title') + ->setBody('ini body') + ) + ->setAndroid( + AndroidConfig::create() + ->setFcmOptions(AndroidFcmOptions::create()->setAnalyticsLabel('analytics')) + ->setNotification(AndroidNotification::create()->setColor('#0A0A0A')) + + )->setApns( + ApnsConfig::create() + ->setFcmOptions(ApnsFcmOptions::create()->setAnalyticsLabel('analytics_ios')) + ); + return $dataFcm; } + + public function fcmProject($notifiable, $message){ + return 'app'; + } + } diff --git a/config/firebase.php b/config/firebase.php new file mode 100644 index 00000000..02b495ac --- /dev/null +++ b/config/firebase.php @@ -0,0 +1,192 @@ + env('FIREBASE_PROJECT', 'app'), + + /* + * ------------------------------------------------------------------------ + * Firebase project configurations + * ------------------------------------------------------------------------ + */ + 'projects' => [ + 'app' => [ + /* + * ------------------------------------------------------------------------ + * Credentials / Service Account + * ------------------------------------------------------------------------ + * + * In order to access a Firebase project and its related services using a + * server SDK, requests must be authenticated. For server-to-server + * communication this is done with a Service Account. + * + * If you don't already have generated a Service Account, you can do so by + * following the instructions from the official documentation pages at + * + * https://firebase.google.com/docs/admin/setup#initialize_the_sdk + * + * Once you have downloaded the Service Account JSON file, you can use it + * to configure the package. + * + * If you don't provide credentials, the Firebase Admin SDK will try to + * auto-discover them + * + * - by checking the environment variable FIREBASE_CREDENTIALS + * - by checking the environment variable GOOGLE_APPLICATION_CREDENTIALS + * - by trying to find Google's well known file + * - by checking if the application is running on GCE/GCP + * + * If no credentials file can be found, an exception will be thrown the + * first time you try to access a component of the Firebase Admin SDK. + * + */ + 'credentials' => [ + 'file' => env('FIREBASE_CREDENTIALS', env('GOOGLE_APPLICATION_CREDENTIALS')), + + /* + * If you want to prevent the auto discovery of credentials, set the + * following parameter to false. If you disable it, you must + * provide a credentials file. + */ + 'auto_discovery' => true, + ], + + /* + * ------------------------------------------------------------------------ + * Firebase Auth Component + * ------------------------------------------------------------------------ + */ + + 'auth' => [ + 'tenant_id' => env('FIREBASE_AUTH_TENANT_ID'), + ], + + /* + * ------------------------------------------------------------------------ + * Firebase Realtime Database + * ------------------------------------------------------------------------ + */ + + 'database' => [ + /* + * In most of the cases the project ID defined in the credentials file + * determines the URL of your project's Realtime Database. If the + * connection to the Realtime Database fails, you can override + * its URL with the value you see at + * + * https://console.firebase.google.com/u/1/project/_/database + * + * Please make sure that you use a full URL like, for example, + * https://my-project-id.firebaseio.com + */ + 'url' => env('FIREBASE_DATABASE_URL'), + + /* + * As a best practice, a service should have access to only the resources it needs. + * To get more fine-grained control over the resources a Firebase app instance can access, + * use a unique identifier in your Security Rules to represent your service. + * + * https://firebase.google.com/docs/database/admin/start#authenticate-with-limited-privileges + */ + // 'auth_variable_override' => [ + // 'uid' => 'my-service-worker' + // ], + ], + + 'dynamic_links' => [ + /* + * Dynamic links can be built with any URL prefix registered on + * + * https://console.firebase.google.com/u/1/project/_/durablelinks/links/ + * + * You can define one of those domains as the default for new Dynamic + * Links created within your project. + * + * The value must be a valid domain, for example, + * https://example.page.link + */ + 'default_domain' => env('FIREBASE_DYNAMIC_LINKS_DEFAULT_DOMAIN'), + ], + + /* + * ------------------------------------------------------------------------ + * Firebase Cloud Storage + * ------------------------------------------------------------------------ + */ + + 'storage' => [ + /* + * Your project's default storage bucket usually uses the project ID + * as its name. If you have multiple storage buckets and want to + * use another one as the default for your application, you can + * override it here. + */ + + 'default_bucket' => env('FIREBASE_STORAGE_DEFAULT_BUCKET'), + ], + + /* + * ------------------------------------------------------------------------ + * Caching + * ------------------------------------------------------------------------ + * + * The Firebase Admin SDK can cache some data returned from the Firebase + * API, for example Google's public keys used to verify ID tokens. + * + */ + + 'cache_store' => env('FIREBASE_CACHE_STORE', 'file'), + + /* + * ------------------------------------------------------------------------ + * Logging + * ------------------------------------------------------------------------ + * + * Enable logging of HTTP interaction for insights and/or debugging. + * + * Log channels are defined in config/logging.php + * + * Successful HTTP messages are logged with the log level 'info'. + * Failed HTTP messages are logged with the the log level 'notice'. + * + * Note: Using the same channel for simple and debug logs will result in + * two entries per request and response. + */ + + 'logging' => [ + 'http_log_channel' => env('FIREBASE_HTTP_LOG_CHANNEL'), + 'http_debug_log_channel' => env('FIREBASE_HTTP_DEBUG_LOG_CHANNEL'), + ], + + /* + * ------------------------------------------------------------------------ + * HTTP Client Options + * ------------------------------------------------------------------------ + * + * Behavior of the HTTP Client performing the API requests + */ + 'http_client_options' => [ + /* + * Use a proxy that all API requests should be passed through. + * (default: none) + */ + 'proxy' => env('FIREBASE_HTTP_CLIENT_PROXY'), + + /* + * Set the maximum amount of seconds (float) that can pass before + * a request is considered timed out + * + * The default time out can be reviewed at + * https://github.com/kreait/firebase-php/blob/6.x/src/Firebase/Http/HttpClientOptions.php + */ + 'timeout' => env('FIREBASE_HTTP_CLIENT_TIMEOUT'), + ], + ], + ], +]; diff --git a/database/migrations/2022_11_22_083926_create_notification_tokens_table.php b/database/migrations/2022_11_22_083926_create_notification_tokens_table.php index 6b29d375..d9a30238 100644 --- a/database/migrations/2022_11_22_083926_create_notification_tokens_table.php +++ b/database/migrations/2022_11_22_083926_create_notification_tokens_table.php @@ -17,6 +17,7 @@ return new class extends Migration $table->id(); $table->morphs('notifiabletoken', 'notifiabletoken'); $table->string('origin'); + $table->string('device_id')->nullable(); $table->string('type'); $table->string('token'); $table->string('status'); @@ -26,7 +27,7 @@ return new class extends Migration $table->unsignedBigInteger('created_by')->nullable()->index(); $table->unsignedBigInteger('updated_by')->nullable()->index(); $table->unsignedBigInteger('deleted_by')->nullable()->index(); - + $table->index('token'); }); } diff --git a/database/migrations/2024_01_03_143801_add_column_to_notifications.php b/database/migrations/2024_01_03_143801_add_column_to_notifications.php new file mode 100644 index 00000000..c98d6a3d --- /dev/null +++ b/database/migrations/2024_01_03_143801_add_column_to_notifications.php @@ -0,0 +1,32 @@ +text('data')->after('notifiable_id')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('notifications', function (Blueprint $table) { + $table->dropColumn('data'); + }); + } +};