diff --git a/config/sms.php b/config/sms.php index 0d42c8b..df18a8e 100644 --- a/config/sms.php +++ b/config/sms.php @@ -3,16 +3,6 @@ use Laraflow\Sms\Providers; return [ - - /* - |-------------------------------------------------------------------------- - | Response Logger - |-------------------------------------------------------------------------- - | this configuration is for debugging purpose. if enabled then program will log - | sms vendor response in debug category. - */ - 'log' => (bool)env('SMS_LOG', env('APP_DEBUG', false)), - /* |-------------------------------------------------------------------------- | Default Vendor @@ -42,6 +32,28 @@ */ 'from' => env('SMS_FROM_NAME', env('APP_NAME', 'Laravel')), + /* + |-------------------------------------------------------------------------- + | Response Logger + |-------------------------------------------------------------------------- + | this configuration is for debugging purpose. if enabled then program will log + | sms vendor response in debug category. + */ + 'log' => (bool)env('SMS_LOG', env('APP_DEBUG', false)), + + /* + |-------------------------------------------------------------------------- + | Response Log Viewer + |-------------------------------------------------------------------------- + | this configuration is for debugging purpose. if enabled then program will log + | sms vendor response in debug category. + */ + 'log_viewer' => [ + 'enabled' => env('SMS_LOG_VIEWER', env('APP_DEBUG', false)), + 'uri' => 'sms-logs', + 'middleware' => null, + ], + /* |-------------------------------------------------------------------------- | Vendor Configuration diff --git a/src/Controllers/SmsLogController.php b/src/Controllers/SmsLogController.php new file mode 100644 index 0000000..dd68f76 --- /dev/null +++ b/src/Controllers/SmsLogController.php @@ -0,0 +1,125 @@ +entries = collect(); + + $this->loadEntries(); + } + + private function loadEntries(): void + { + foreach (Storage::disk('log')->files() as $file) { + if (!str_contains($file, 'sms-')) { + continue; + } + + $date = CarbonImmutable::createFromFormat('Y-m-d', preg_replace('/sms-(\d{4}-\d{2}-\d{2})\.log/i', '$1', $file)); + + $this->entries->put($date->format('Y-m-d'), [ + 'position' => now()->diffInDays($date), + 'file' => $file, + 'date' => $date, + 'size' => Storage::disk('log')->size($file), + 'modified_at' => CarbonImmutable::parse(Storage::disk('log')->lastModified($file)), + ]); + } + + $this->entries = $this->entries->sortBy('position'); + } + + /** + * Display a listing of the resource. + */ + public function index(Request $request) + { + $current = $request->has('date') ? $this->entries->get($request->input('date')) : $this->entries->first(); + + if (!$current) { + throw (new ModelNotFoundException)->setModel('SmsLog', $request->input('date', now()->format('Y-m-d'))); + } + + $current['logs'] = $this->parseFileContent($current['file']); + + return view('sms::index', [ + 'entries' => $this->entries, + 'current' => $current + ]); + } + + /** + * Display the specified resource. + */ + public function download(string $date) + { + // + } + + /** + * Remove the specified resource from storage. + */ + public function destroy(string $id) + { + // + } + + private function parseFileContent(string $file): Collection + { + $file = Storage::disk('log')->readStream($file); + + $entries = collect(); + + while (!feof($file)) { + $item = $this->parseLine(fgets($file)); + + if (!empty($item)) { + $entries->push($item); + } + } + + return $entries->sortBy('position'); + } + + private function parseLine(string $line): array + { + $pattern = '/\[(\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2})\] .*\.INFO:\sResponse:\s+(.*)/'; + + $matches = []; + + preg_match($pattern, $line, $matches); + + $data = []; + + if (!empty($matches)) { + + $timestamp = CarbonImmutable::parse($matches[1] ?? null, 'Asia/Dhaka'); + + $data['position'] = now()->diffInSeconds($timestamp); + $data['timestamp'] = $timestamp; + + $response = json_decode($matches[2] ?? '{}', true); + $data['vendor'] = Str::studly($response['vendor'] ?? 'N/A'); + $data['mode'] = Str::studly($response['mode'] ?? 'N/A'); + $data['code'] = Str::studly($response['status_code'] ?? 'N/A'); + $data['response'] = json_encode($response['response'] ?? [], JSON_PRETTY_PRINT); + } + + return $data; + } +} diff --git a/src/SmsChannel.php b/src/SmsChannel.php index b396446..f8cf1d3 100644 --- a/src/SmsChannel.php +++ b/src/SmsChannel.php @@ -59,20 +59,31 @@ private function logSmsResponse(): void if (config('sms.log', false)) { Log::channel('sms')->info('Response: ', [ 'vendor' => $this->driver_code, - 'model' => $this->driver->mode, + 'mode' => $this->driver->mode, 'status_code' => $this->response->status(), - 'response' => $this->response->body() + 'response' => $this->processResponseBody($this->response->body()) ]); } } + private function processResponseBody($content) + { + $dump = json_decode($content, true); + + if (json_last_error() == JSON_ERROR_NONE) { + return $dump; + } + + return $content; + } + private function validate(SmsMessage $message): void { - if (strlen($message->getReceiver()) == 0) { + if (empty($message->getReceiver())) { throw new InvalidArgumentException('Message recipient(s) is empty.'); } - if (strlen($message->getContent()) == 0) { + if (empty($message->getContent())) { throw new InvalidArgumentException('Message content is empty.'); } } @@ -95,6 +106,12 @@ public function send(object $notifiable, Notification $notification): void */ $message = $notification->toSms($notifiable); + if (!$to = $notifiable->routeNotificationFor('sms', $notification)) { + throw new BadMethodCallException(get_class($notifiable) . " notifiable is missing the `routeNotificationForSms(object $notifiable): string` method."); + } + + $message->to($to); + $this->initDriver($message->getDriver()); $this->validate($message); diff --git a/src/SmsMessage.php b/src/SmsMessage.php index b261394..4bff33a 100644 --- a/src/SmsMessage.php +++ b/src/SmsMessage.php @@ -14,12 +14,12 @@ class SmsMessage private ?string $driver; - public function __construct() + public function __construct(array $options = []) { - $this->receiver = null; - $this->sender = config('sms.from', config('app.name')); - $this->content = null; - $this->driver = config('sms.default'); + $this->receiver = $options['to'] ?? null; + $this->sender = $options['from'] ?? config('sms.from', config('app.name')); + $this->content = $options['message'] ?? null; + $this->driver = $options['vendor'] ?? config('sms.default'); } public function getReceiver(): ?string @@ -73,4 +73,9 @@ public function vendor($name = null): self return $this; } + + public function send() + { + + } } diff --git a/src/SmsServiceProvider.php b/src/SmsServiceProvider.php index bdf1cc7..fc55931 100644 --- a/src/SmsServiceProvider.php +++ b/src/SmsServiceProvider.php @@ -31,6 +31,10 @@ public function boot(): void ], 'sms-config'); $this->extendNotificationChannel(); + + $this->loadRoutesFrom(__DIR__.'/routes.php'); + + $this->loadViewsFrom(__DIR__.'/../view', 'sms'); } /** @@ -44,9 +48,9 @@ private function extendNotificationChannel(): void }); } - private function extendLoggerChannel() + private function extendLoggerChannel(): void { - Config::set('sms', [ + Config::set('logging.channels.sms', [ 'driver' => 'daily', 'path' => storage_path('logs/sms.log'), 'level' => 'info', diff --git a/src/routes.php b/src/routes.php new file mode 100644 index 0000000..184c5bb --- /dev/null +++ b/src/routes.php @@ -0,0 +1,11 @@ +group(function () { + Route::get('/', [\Laraflow\Sms\Controllers\SmsLogController::class, 'index'])->name('sms-logs.index'); + Route::get('/{date}', [\Laraflow\Sms\Controllers\SmsLogController::class, 'show'])->name('sms-logs.show'); + Route::delete('/{date}', [\Laraflow\Sms\Controllers\SmsLogController::class, 'destroy'])->name('sms-logs.destroy'); + }); +} diff --git a/view/index.blade.php b/view/index.blade.php new file mode 100644 index 0000000..a5fa912 --- /dev/null +++ b/view/index.blade.php @@ -0,0 +1,42 @@ +@extends('sms::layout') + +@section('content') +
+

{{ $current['date']->format('D, d M Y') }}

+
+ + +
+
+
+ + + + + + + + + + + + @foreach($current['logs'] as $response) + + + + + + + + @endforeach + +
TimestampVendorModeCodeResponse
{{ $response['timestamp']->format('D, d M Y h:m:s A') }}{{ $response['vendor'] ?? 'N/A' }}{{ $response['mode'] ?? 'N/A' }}{{ $response['code'] ?? 'N/A' }}
{{ $response['response'] ?? '{}' }}
+
+@endsection diff --git a/view/layout.blade.php b/view/layout.blade.php new file mode 100644 index 0000000..dc90fc3 --- /dev/null +++ b/view/layout.blade.php @@ -0,0 +1,120 @@ + + + + + + + + + SMS LOG Viewer | {{ config('app.name') }} + + + + + +
+
+ +
+ @yield('content') +
+
+
+ + + + + + +