diff --git a/src/Commands/ClearMailbaseCommand.php b/src/Commands/ClearMailbaseCommand.php index 3409d2b..fe8f1b3 100644 --- a/src/Commands/ClearMailbaseCommand.php +++ b/src/Commands/ClearMailbaseCommand.php @@ -1,8 +1,11 @@ line('Clearing stored Mailbase emails.'); Mailbase::truncate(); - $this->info('Cleared stored Mailbase emails.'); + $disk = Storage::disk(config('mailbase.disk', 'mailbase')); + foreach ($disk->allDirectories() as $dir) { + $disk->deleteDirectory($dir); + } + + $this->info('Cleared stored Mailbase emails and attachments.'); } } diff --git a/src/MailController.php b/src/MailController.php index a163796..fd33c25 100644 --- a/src/MailController.php +++ b/src/MailController.php @@ -4,7 +4,12 @@ namespace Tkeer\Mailbase; +use Exception; +use Illuminate\Http\JsonResponse; use Illuminate\Routing\Controller; +use Illuminate\Support\Facades\Storage; +use Symfony\Component\HttpFoundation\StreamedResponse; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; class MailController extends Controller { @@ -15,11 +20,76 @@ public function index() return view('mailbase::index', ['mails' => $mails]); } - public function show(Mailbase $mailbase) + public function show(Mailbase $mailbase): JsonResponse { $mailbase->update(['is_read' => 1]); return response()->json($mailbase); } + public function attachment(Mailbase $mailbase, int $index): StreamedResponse + { + $attachment = $this->resolveAttachment($mailbase, $index); + $disk = Storage::disk(config('mailbase.disk', 'mailbase')); + + return $disk->response($attachment['storage_path'], $attachment['filename'], [ + 'Content-Type' => $attachment['mime_type'], + ]); + } + + public function downloadAttachment(Mailbase $mailbase, int $index): StreamedResponse + { + $attachment = $this->resolveAttachment($mailbase, $index); + $disk = Storage::disk(config('mailbase.disk', 'mailbase')); + + return $disk->download($attachment['storage_path'], $attachment['filename']); + } + + /** + * Clear all emails from the database and delete stored attachments. + */ + public function clear(): JsonResponse + { + try { + Mailbase::truncate(); + + $disk = Storage::disk(config('mailbase.disk', 'mailbase')); + foreach ($disk->allDirectories() as $dir) { + $disk->deleteDirectory($dir); + } + + return response()->json([ + 'success' => true, + 'message' => 'All emails have been cleared successfully.', + ]); + } catch (Exception $e) { + return response()->json([ + 'success' => false, + 'message' => 'Failed to clear emails: ' . $e->getMessage(), + ], 500); + } + } + + /** + * Resolve an attachment from the mail's JSON metadata by index. + * + * @return array{filename: string, mime_type: string, size: int, storage_path: string} + */ + protected function resolveAttachment(Mailbase $mailbase, int $index): array + { + $attachments = json_decode($mailbase->attachments ?? '[]', true); + + if (! is_array($attachments) || ! isset($attachments[$index])) { + throw new NotFoundHttpException('Attachment not found.'); + } + + $attachment = $attachments[$index]; + $disk = Storage::disk(config('mailbase.disk', 'mailbase')); + + if (! $disk->exists($attachment['storage_path'])) { + throw new NotFoundHttpException('Attachment file not found on disk.'); + } + + return $attachment; + } } diff --git a/src/MailbaseServiceProvider.php b/src/MailbaseServiceProvider.php index bd66350..0040d7a 100644 --- a/src/MailbaseServiceProvider.php +++ b/src/MailbaseServiceProvider.php @@ -1,26 +1,43 @@ mergeConfigFrom(__DIR__ . '/config/mailbase.php', 'mailbase'); + } + + public function boot(): void { - // add `mailbase` to mailers config + // Add `mailbase` to mailers config config([ - 'mail.mailers.mailbase' => ['transport' => 'mailbase'] + 'mail.mailers.mailbase' => ['transport' => 'mailbase'], ]); app(MailManager::class)->extend('mailbase', function ($app) { return new MailbaseTransport(); }); + // Auto-register the mailbase filesystem disk if not already configured + $diskName = config('mailbase.disk', 'mailbase'); + if (! config("filesystems.disks.{$diskName}")) { + config([ + "filesystems.disks.{$diskName}" => [ + 'driver' => 'local', + 'root' => config('mailbase.storage_path', storage_path('app/mailbase')), + ], + ]); + } + $this->loadMigrationsFrom(__DIR__ . '/migrations/'); $this->loadRoutesFrom(__DIR__ . '/routes.php'); $this->loadViewsFrom(__DIR__ . '/views', 'mailbase'); @@ -30,6 +47,10 @@ public function boot() ClearMailbaseCommand::class, TestMailbaseCommand::class, ]); + + $this->publishes([ + __DIR__ . '/config/mailbase.php' => config_path('mailbase.php'), + ], 'mailbase-config'); } } } diff --git a/src/MailbaseTransport.php b/src/MailbaseTransport.php index 6bf38ee..49111ed 100644 --- a/src/MailbaseTransport.php +++ b/src/MailbaseTransport.php @@ -1,7 +1,11 @@ getOriginalMessage(); $subject = $email->getSubject(); @@ -20,9 +22,10 @@ protected function doSend(SentMessage $message): void $cc = collect($email->getCc())->map->toString()->implode("\n"); $bcc = collect($email->getBcc())->map->toString()->implode("\n"); $body = $email->getHtmlBody() ?: $email->getTextBody(); - $attachments = collect($email->getAttachments())->toJson(); $headers = $email->getHeaders()->toString(); + $attachments = $this->storeAttachments($email->getAttachments()); + Mailbase::create([ 'from' => $from, 'to' => $to, @@ -31,11 +34,42 @@ protected function doSend(SentMessage $message): void 'subject' => $subject, 'body' => $body, 'headers' => $headers, - 'attachments' => $attachments, + 'attachments' => json_encode($attachments), 'sent_at' => now()->toDateTimeString(), ]); } + /** + * Store attachment files to disk and return metadata array. + * + * @param iterable<\Symfony\Component\Mime\Part\DataPart> $parts + * @return array + */ + protected function storeAttachments(iterable $parts): array + { + $disk = Storage::disk(config('mailbase.disk', 'mailbase')); + $date = now()->format('Y-m-d'); + $attachments = []; + + foreach ($parts as $part) { + $filename = $part->getFilename() ?? 'attachment'; + $content = $part->getBody(); + $mimeType = $part->getContentType(); + + $storagePath = $date . '/' . Str::uuid() . '_' . $filename; + $disk->put($storagePath, $content); + + $attachments[] = [ + 'filename' => $filename, + 'mime_type' => $mimeType, + 'size' => strlen($content), + 'storage_path' => $storagePath, + ]; + } + + return $attachments; + } + public function __toString(): string { return 'mailbase'; diff --git a/src/config/mailbase.php b/src/config/mailbase.php new file mode 100644 index 0000000..b1b1391 --- /dev/null +++ b/src/config/mailbase.php @@ -0,0 +1,33 @@ + env('MAILBASE_DISK', 'mailbase'), + + /* + |-------------------------------------------------------------------------- + | Storage Path + |-------------------------------------------------------------------------- + | + | The local path used when Mailbase auto-registers its filesystem disk. + | This is only used if the disk above is not already configured in + | your application's filesystems.php config file. + | + */ + + 'storage_path' => storage_path('app/mailbase'), + +]; \ No newline at end of file diff --git a/src/routes.php b/src/routes.php index 309c6da..8d09c6d 100644 --- a/src/routes.php +++ b/src/routes.php @@ -7,5 +7,8 @@ Route::group(['as' => 'mailbase::', 'prefix' => 'mailbase', 'middleware' => SubstituteBindings::class], function () { Route::get('/', MailController::class . '@index')->name('index'); + Route::post('/clear', MailController::class . '@clear')->name('clear'); + Route::get('/{mailbase}/attachments/{index}', MailController::class . '@attachment')->name('attachment'); + Route::get('/{mailbase}/attachments/{index}/download', MailController::class . '@downloadAttachment')->name('attachment.download'); Route::get('/{mailbase}', MailController::class . '@show')->name('show'); }); diff --git a/src/views/index.blade.php b/src/views/index.blade.php index 254a4bc..4026e81 100644 --- a/src/views/index.blade.php +++ b/src/views/index.blade.php @@ -2,138 +2,587 @@ - + {{ config('app.name') }} - Mail Viewer - - + + - + -
-

{{ config('app.name') }} - Outgoing mails

-
+ +
+
+
+
+ +

{{ config('app.name') }} - Outgoing Mails

+
+
+ + + + +
+
+
+
+ +
-
-
+ +
-
+ +
+
+

+ + Inbox +

+ + {{ $mails->total() ?? 0 }} + +
+
+ +
@forelse($mails as $mail) -
-
- {{ $mail->subject }} -
-
-
- {{ $mail->to }} +
+ +
+
+
+ {{ strtoupper(substr($mail->to, 0, 1)) }} +
+
+

{{ $mail->to }}

+
-
+
+ @if(!$mail->is_read) +
+ @endif + +
+
+ +

+ {{ $mail->subject ?: 'No Subject' }} +

+ +
+
+ {{ $mail->sent_at->diffForHumans() }}
+ @php $attachmentList = json_decode($mail->attachments, true) ?? []; @endphp + @if(count($attachmentList) > 0) + + + {{ count($attachmentList) }} + + @endif
@empty - No mail found +
+ +

No mails found

+
@endforelse - -
+ + @if($mails->hasPages()) +
+
+
+ @if($mails->onFirstPage()) + + @else + + + + @endif + + + {{ $mails->currentPage() }} of {{ $mails->lastPage() }} + + + @if($mails->hasMorePages()) + + + + @else + + @endif +
+ +
+ Showing {{ $mails->firstItem() ?? 0 }}-{{ $mails->lastItem() ?? 0 }} of {{ $mails->total() ?? 0 }} +
+
+
+ @endif
+ + - + + + - + \ No newline at end of file