Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions src/Commands/ClearMailbaseCommand.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
<?php

declare(strict_types=1);

namespace Tkeer\Mailbase\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;
use Tkeer\Mailbase\Mailbase;

class ClearMailbaseCommand extends Command
Expand All @@ -19,22 +22,19 @@ class ClearMailbaseCommand extends Command
*
* @var string
*/
protected $description = 'Delete all emails stored by Mailbase in the database.';
protected $description = 'Delete all emails and attachment files stored by Mailbase.';

/**
* Execute the console command. Attempt to delete
* all of the Mailbase items stored in the DB.
* If an exception is thrown, catch it and
* display it in the console.
*
* @return void
*/
public function handle()
public function handle(): void
{
$this->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.');
}
}
72 changes: 71 additions & 1 deletion src/MailController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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;
}
}
31 changes: 26 additions & 5 deletions src/MailbaseServiceProvider.php
Original file line number Diff line number Diff line change
@@ -1,26 +1,43 @@
<?php

declare(strict_types=1);

namespace Tkeer\Mailbase;

use Illuminate\Mail\MailManager;
use Tkeer\Mailbase\Commands\ClearMailbaseCommand;
use Illuminate\Mail\MailServiceProvider;
use Illuminate\Support\ServiceProvider;
use Tkeer\Mailbase\Commands\TestMailbaseCommand;
use Illuminate\Support\ServiceProvider;

class MailbaseServiceProvider extends ServiceProvider
{
public function boot()
public function register(): void
{
$this->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');
Expand All @@ -30,6 +47,10 @@ public function boot()
ClearMailbaseCommand::class,
TestMailbaseCommand::class,
]);

$this->publishes([
__DIR__ . '/config/mailbase.php' => config_path('mailbase.php'),
], 'mailbase-config');
}
}
}
44 changes: 39 additions & 5 deletions src/MailbaseTransport.php
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
<?php

declare(strict_types=1);

namespace Tkeer\Mailbase;

use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Symfony\Component\Mailer\SentMessage;
use Symfony\Component\Mailer\Transport\AbstractTransport;

class MailbaseTransport extends AbstractTransport
{
protected function doSend(SentMessage $message): void
{
/**
* @var $email \Symfony\Component\Mime\Email
*/
/** @var \Symfony\Component\Mime\Email $email */
$email = $message->getOriginalMessage();

$subject = $email->getSubject();
Expand All @@ -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,
Expand All @@ -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<int, array{filename: string, mime_type: string, size: int, storage_path: string}>
*/
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';
Expand Down
33 changes: 33 additions & 0 deletions src/config/mailbase.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

return [

/*
|--------------------------------------------------------------------------
| Storage Disk
|--------------------------------------------------------------------------
|
| The filesystem disk used to store email attachments. If this disk is not
| defined in your filesystems.php config, Mailbase will automatically
| register a local disk pointing to the storage_path below.
|
*/

'disk' => 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'),

];
3 changes: 3 additions & 0 deletions src/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
Loading