126 lines
5.0 KiB
PHP
126 lines
5.0 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Messaging;
|
|
|
|
use App\Events\ConversationUpdated;
|
|
use App\Events\MessageCreated;
|
|
use App\Models\Conversation;
|
|
use App\Models\Message;
|
|
use App\Models\MessageAttachment;
|
|
use App\Models\User;
|
|
use App\Services\Messaging\ConversationStateService;
|
|
use Illuminate\Http\UploadedFile;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Storage;
|
|
|
|
class SendMessageAction
|
|
{
|
|
public function __construct(
|
|
private readonly ConversationStateService $conversationState,
|
|
private readonly MessageNotificationService $notifications,
|
|
private readonly MessageSearchIndexer $searchIndexer,
|
|
) {}
|
|
|
|
public function execute(Conversation $conversation, User $sender, array $payload): Message
|
|
{
|
|
$body = trim((string) ($payload['body'] ?? ''));
|
|
$files = $payload['attachments'] ?? [];
|
|
|
|
/** @var Message $message */
|
|
$message = DB::transaction(function () use ($conversation, $sender, $payload, $body, $files) {
|
|
$message = Message::query()->create([
|
|
'conversation_id' => $conversation->id,
|
|
'sender_id' => $sender->id,
|
|
'client_temp_id' => $payload['client_temp_id'] ?? null,
|
|
'message_type' => empty($files) ? 'text' : ($body === '' ? 'attachment' : 'text'),
|
|
'body' => $body,
|
|
'reply_to_message_id' => $payload['reply_to_message_id'] ?? null,
|
|
]);
|
|
|
|
foreach ($files as $file) {
|
|
if ($file instanceof UploadedFile) {
|
|
$this->storeAttachment($file, $message, $sender->id);
|
|
}
|
|
}
|
|
|
|
$conversation->forceFill([
|
|
'last_message_id' => $message->id,
|
|
'last_message_at' => $message->created_at,
|
|
])->save();
|
|
|
|
return $message;
|
|
});
|
|
|
|
$participantIds = $this->conversationState->activeParticipantIds($conversation);
|
|
$this->conversationState->touchConversationCachesForUsers($participantIds);
|
|
|
|
DB::afterCommit(function () use ($conversation, $message, $sender, $participantIds): void {
|
|
$this->notifications->notifyNewMessage($conversation, $message, $sender);
|
|
$this->searchIndexer->indexMessage($message);
|
|
|
|
event(new MessageCreated($conversation, $message, $sender->id));
|
|
|
|
foreach ($participantIds as $participantId) {
|
|
event(new ConversationUpdated($participantId, $conversation, 'message.created'));
|
|
}
|
|
});
|
|
|
|
return $message->fresh(['sender:id,username,name', 'attachments', 'reactions']);
|
|
}
|
|
|
|
private function storeAttachment(UploadedFile $file, Message $message, int $userId): void
|
|
{
|
|
$mime = (string) $file->getMimeType();
|
|
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
|
$finfoMime = $finfo ? (string) finfo_file($finfo, $file->getPathname()) : '';
|
|
if ($finfo) {
|
|
finfo_close($finfo);
|
|
}
|
|
$detectedMime = $finfoMime !== '' ? $finfoMime : $mime;
|
|
|
|
$allowedImage = (array) config('messaging.attachments.allowed_image_mimes', []);
|
|
$allowedFile = (array) config('messaging.attachments.allowed_file_mimes', []);
|
|
|
|
$type = in_array($detectedMime, $allowedImage, true) ? 'image' : 'file';
|
|
$allowed = $type === 'image' ? $allowedImage : $allowedFile;
|
|
|
|
abort_unless(in_array($detectedMime, $allowed, true), 422, 'Unsupported attachment type.');
|
|
|
|
$maxBytes = $type === 'image'
|
|
? ((int) config('messaging.attachments.max_image_kb', 10240) * 1024)
|
|
: ((int) config('messaging.attachments.max_file_kb', 25600) * 1024);
|
|
|
|
abort_if($file->getSize() > $maxBytes, 422, 'Attachment exceeds allowed size.');
|
|
|
|
$year = now()->format('Y');
|
|
$month = now()->format('m');
|
|
$ext = strtolower($file->getClientOriginalExtension() ?: $file->extension() ?: 'bin');
|
|
$path = "messages/{$message->conversation_id}/{$year}/{$month}/" . uniqid('att_', true) . ".{$ext}";
|
|
$diskName = (string) config('messaging.attachments.disk', 'local');
|
|
|
|
Storage::disk($diskName)->put($path, file_get_contents($file->getPathname()));
|
|
|
|
$width = null;
|
|
$height = null;
|
|
if ($type === 'image') {
|
|
$dimensions = @getimagesize($file->getPathname());
|
|
$width = isset($dimensions[0]) ? (int) $dimensions[0] : null;
|
|
$height = isset($dimensions[1]) ? (int) $dimensions[1] : null;
|
|
}
|
|
|
|
MessageAttachment::query()->create([
|
|
'message_id' => $message->id,
|
|
'disk' => $diskName,
|
|
'user_id' => $userId,
|
|
'type' => $type,
|
|
'mime' => $detectedMime,
|
|
'size_bytes' => (int) $file->getSize(),
|
|
'width' => $width,
|
|
'height' => $height,
|
|
'sha256' => hash_file('sha256', $file->getPathname()),
|
|
'original_name' => substr((string) $file->getClientOriginalName(), 0, 255),
|
|
'storage_path' => $path,
|
|
'created_at' => now(),
|
|
]);
|
|
}
|
|
} |