• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

LeTraceurSnorkLibrary / MessSaga / 24455793340

15 Apr 2026 12:57PM UTC coverage: 39.248% (+2.7%) from 36.562%
24455793340

Pull #20

github

web-flow
Merge 4e3783ced into 4ac78237e
Pull Request #20: feat: tariffs

161 of 280 new or added lines in 14 files covered. (57.5%)

2 existing lines in 2 files now uncovered.

741 of 1888 relevant lines covered (39.25%)

0.78 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/app/Jobs/ProcessConversationMediaUpload.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace App\Jobs;
6

7
use App\Models\Conversation;
8
use App\Models\MediaAttachment;
9
use App\Models\MediaTypes\SupportedMediaTypesEnum;
10
use App\Models\User;
11
use App\Services\Import\Archives\Exceptions\ArchiveExtractionFailedException;
12
use App\Services\Import\Factories\ImportArchiveExtractorFactory;
13
use App\Services\Media\ImportedMediaResolverService;
14
use App\Services\Media\Storage\MediaStorageInterface;
15
use App\Services\Parsers\ParserRegistry;
16
use App\Services\Quota\UserMediaQuotaService;
17
use Illuminate\Bus\Queueable;
18
use Illuminate\Contracts\Queue\ShouldQueue;
19
use Illuminate\Foundation\Bus\Dispatchable;
20
use Illuminate\Queue\InteractsWithQueue;
21
use Illuminate\Queue\SerializesModels;
22
use Illuminate\Support\Facades\Log;
23
use Illuminate\Support\Facades\Storage;
24

25
class ProcessConversationMediaUpload implements ShouldQueue
26
{
27
    use Dispatchable;
28
    use InteractsWithQueue;
29
    use Queueable;
30
    use SerializesModels;
31

32
    /**
33
     * @param int    $userId
34
     * @param int    $conversationId
35
     * @param string $path
36
     */
37
    public function __construct(
38
        public int    $userId,
39
        public int    $conversationId,
40
        public string $path
41
    ) {
42
    }
×
43

44
    /**
45
     * @param ParserRegistry                $parserRegistry
46
     * @param ImportedMediaResolverService  $importedMediaResolverService
47
     * @param MediaStorageInterface         $mediaStorage
48
     * @param ImportArchiveExtractorFactory $archiveExtractorsFactory
49
     * @param UserMediaQuotaService         $userMediaQuotaService
50
     *
51
     * @return void
52
     */
53
    public function handle(
54
        ParserRegistry                $parserRegistry,
55
        ImportedMediaResolverService  $importedMediaResolverService,
56
        MediaStorageInterface         $mediaStorage,
57
        ImportArchiveExtractorFactory $archiveExtractorsFactory,
58
        UserMediaQuotaService         $userMediaQuotaService
59
    ): void {
60
        $importsTmpDiskName = (string)config('filesystems.imports_tmp_disk', 'imports_tmp');
×
61
        $importsTmpDisk     = Storage::disk($importsTmpDiskName);
×
62
        $extractedDir       = null;
×
63

64
        $conversation = Conversation::with('messengerAccount')->find($this->conversationId);
×
65
        if (!$conversation || $conversation->messengerAccount->user_id !== $this->userId) {
×
66
            return;
×
67
        }
68

NEW
69
        $user = User::find($this->userId);
×
NEW
70
        if (!$user) {
×
NEW
71
            return;
×
72
        }
NEW
73
        $quota = $userMediaQuotaService->snapshot($user);
×
NEW
74
        if (!$quota->canUploadMedia()) {
×
NEW
75
            return;
×
76
        }
NEW
77
        $remainingStorageBytes = $quota->getRemainingStorageBytes();
×
NEW
78
        $remainingMediaFiles   = $quota->getRemainingFilesCount();
×
79

80
        try {
81
            $archiveExtractor = $archiveExtractorsFactory->makeForPath($this->path);
×
82
            if ($archiveExtractor === null) {
×
83
                return;
×
84
            }
85

86
            $source       = $archiveExtractor->extract($this->path, $conversation->messengerAccount->type);
×
87
            $extractedDir = $source->getExtractedDir();
×
88
            if ($extractedDir === null) {
×
89
                return;
×
90
            }
91

92
            // Для догрузки медиа нужен весь распакованный архив, а не messenger-specific media root.
93
            $absoluteExtracted = $importsTmpDisk->path($extractedDir);
×
94

95
            $parser   = $parserRegistry->get($conversation->messengerAccount->type);
×
96
            $relation = $parser->getMessagesRelation($conversation);
×
97
            $model    = $relation->getRelated();
×
98

99
            $pending = MediaAttachment::query()
×
100
                ->where('conversation_id', $conversation->id)
×
101
                ->whereNotNull('export_path')
×
102
                ->where(function ($q): void {
×
103
                    $q->whereNull('stored_path')->orWhere('stored_path', '');
×
104
                })
×
105
                ->orderBy('id')
×
106
                ->get();
×
107

108
            $messageIdByAttachmentId = $model->newQuery()
×
109
                ->whereIn('media_attachment_id', $pending->pluck('id')->all())
×
110
                ->pluck('id', 'media_attachment_id');
×
111

NEW
112
            $candidates = [];
×
113
            foreach ($pending as $media) {
×
114
                $messageId = $messageIdByAttachmentId->get($media->id);
×
115
                if ($messageId === null) {
×
116
                    continue;
×
117
                }
118

NEW
119
                $sizeBytes = $importedMediaResolverService->estimateAttachmentSizeBytes(
×
NEW
120
                    $absoluteExtracted,
×
NEW
121
                    (string)$media->export_path
×
NEW
122
                );
×
NEW
123
                if ($sizeBytes === null || $sizeBytes < 0) {
×
NEW
124
                    continue;
×
125
                }
126

NEW
127
                $candidates[] = [
×
NEW
128
                    'media'      => $media,
×
NEW
129
                    'message_id' => (int)$messageId,
×
NEW
130
                    'size_bytes' => $sizeBytes,
×
NEW
131
                ];
×
132
            }
133

NEW
134
            usort($candidates, static fn(array $a, array $b): int => $a['size_bytes'] <=> $b['size_bytes']);
×
135

NEW
136
            foreach ($candidates as $candidate) {
×
NEW
137
                if ($remainingMediaFiles <= 0 || $remainingStorageBytes <= 0) {
×
NEW
138
                    break;
×
139
                }
140

NEW
141
                $sizeBytes = (int)$candidate['size_bytes'];
×
NEW
142
                if ($sizeBytes > $remainingStorageBytes) {
×
NEW
143
                    continue;
×
144
                }
145

146
                /**
147
                 * @var MediaAttachment $media
148
                 */
NEW
149
                $media     = $candidate['media'];
×
NEW
150
                $messageId = (int)$candidate['message_id'];
×
151

152
                $storedPath = $importedMediaResolverService->copyForMessage(
×
153
                    $absoluteExtracted,
×
154
                    (string)$media->export_path,
×
155
                    $conversation->id,
×
NEW
156
                    $messageId
×
157
                );
×
158
                if ($storedPath === null) {
×
159
                    continue;
×
160
                }
161

162
                $mime = $mediaStorage->mimeType($storedPath);
×
163
                $media->update([
×
164
                    'stored_path'       => $storedPath,
×
165
                    'media_type'        => SupportedMediaTypesEnum::detect($mime
×
166
                        ?: null, $media->export_path)?->value,
×
167
                    'mime_type'         => $mime
×
168
                        ?: null,
×
169
                    'original_filename' => basename($storedPath),
×
NEW
170
                    'size_bytes'        => $sizeBytes,
×
UNCOV
171
                ]);
×
172

NEW
173
                $remainingStorageBytes -= $sizeBytes;
×
NEW
174
                $remainingMediaFiles--;
×
175
            }
176
        } catch (ArchiveExtractionFailedException $e) {
×
177
            Log::warning('Archive extraction failed', [
×
178
                'user_id'         => $this->userId,
×
179
                'conversation_id' => $this->conversationId,
×
180
                'path'            => $this->path,
×
181
                'reason'          => $e->getMessage(),
×
182
            ]);
×
183
        } finally {
184
            if (isset($extractedDir) && $importsTmpDisk->exists($extractedDir)) {
×
185
                $importsTmpDisk->deleteDirectory($extractedDir);
×
186
            }
187
            if ($importsTmpDisk->exists($this->path)) {
×
188
                $importsTmpDisk->delete($this->path);
×
189
            }
190
        }
191
    }
192
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc