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

LeTraceurSnorkLibrary / MessSaga / 23852870478

01 Apr 2026 02:06PM UTC coverage: 31.268% (-0.4%) from 31.674%
23852870478

Pull #15

github

web-flow
Merge 57d62f151 into a7d6f3637
Pull Request #15: feat: add s3 as storage

14 of 43 new or added lines in 8 files covered. (32.56%)

2 existing lines in 2 files now uncovered.

439 of 1404 relevant lines covered (31.27%)

0.73 hits per line

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

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

3
declare(strict_types=1);
4

5
namespace App\Jobs;
6

7
use App\Services\Import\Archives\DTO\ArchiveExtractionResult;
8
use App\Services\Import\Archives\Exceptions\ArchiveExtractionFailedException;
9
use App\Services\Import\Factories\ImportArchiveExtractorFactory;
10
use App\Services\Import\Strategies\ImportOnlyMediaFilesStrategyInterface;
11
use App\Services\Import\Strategies\ImportStrategyInterface;
12
use App\Services\ImportService;
13
use App\Services\Media\ImportedMediaResolverService;
14
use App\Services\Media\Storage\MediaStorageInterface;
15
use App\Services\Parsers\ParserRegistry;
16
use Illuminate\Bus\Queueable;
17
use Illuminate\Contracts\Queue\ShouldQueue;
18
use Illuminate\Database\QueryException;
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 ProcessChatImport implements ShouldQueue
26
{
27
    use Dispatchable;
28
    use InteractsWithQueue;
29
    use Queueable;
30
    use SerializesModels;
31

32
    /**
33
     * @param int                     $userId
34
     * @param string                  $messengerType
35
     * @param string                  $exportFileStoredPath Storage-relative path (archive or single export file)
36
     * @param ImportStrategyInterface $strategy
37
     */
38
    public function __construct(
39
        public int                     $userId,
40
        public string                  $messengerType,
41
        public string                  $exportFileStoredPath,
42
        public ImportStrategyInterface $strategy
43
    ) {
44
    }
×
45

46
    /**
47
     * @param ImportArchiveExtractorFactory $archiveExtractorsFactory
48
     * @param ParserRegistry                $parserRegistry
49
     * @param ImportedMediaResolverService  $importedMediaResolverService
50
     * @param MediaStorageInterface         $mediaStorage
51
     *
52
     * @return void
53
     */
54
    public function handle(
55
        ImportArchiveExtractorFactory $archiveExtractorsFactory,
56
        ParserRegistry                $parserRegistry,
57
        ImportedMediaResolverService  $importedMediaResolverService,
58
        MediaStorageInterface         $mediaStorage
59
    ): void {
60
        $source             = null;
×
61
        $extractedDir       = null;
×
62
        $shouldDeleteSource = true;
×
63

64
        try {
65
            $archiveExtractor = $archiveExtractorsFactory->makeForPath($this->exportFileStoredPath);
×
66
            if ($archiveExtractor !== null) {
×
67
                $source = $archiveExtractor->extract($this->exportFileStoredPath, $this->messengerType);
×
68

69
                $extractedDir = $source->getExtractedDir();
×
70

71
                /**
72
                 * If export file (e.g. result.json) is not presented - we cannot perform import
73
                 */
74
                if ($source->getExportFileAbsolutePath() === null) {
×
75
                    /**
76
                     * Though, if import mode is 'To selected conversation' - we can try to import media to already
77
                     * existing conversation
78
                     */
79
                    $this->runMediaOnlyFallback(
×
80
                        archiveExtractorsFactory: $archiveExtractorsFactory,
×
81
                        parserRegistry: $parserRegistry,
×
NEW
82
                        importedMediaResolverService: $importedMediaResolverService,
×
NEW
83
                        mediaStorage: $mediaStorage
×
UNCOV
84
                    );
×
85

86
                    return;
×
87
                }
88
            }
89
            $extractedExportFile = $source ?? new ArchiveExtractionResult(
×
90
                Storage::path($this->exportFileStoredPath),
×
91
                null,
×
92
                null
×
93
            );
×
94

95
            /**
96
             * @var ImportService $service
97
             */
98
            $service = app(ImportService::class);
×
99

100
            /**
101
             * Импорт читает файл экспорта из $extractedExportFile и при наличии медиа копирует их из распакованной
102
             * папки в постоянное хранилище (Storage).
103
             * К моменту выхода из import() файлы уже лежат в conversations/{id}/media/.
104
             */
105
            $service->import(
×
106
                userId: $this->userId,
×
107
                messengerType: $this->messengerType,
×
108
                strategy: $this->strategy,
×
109
                extractedExportFile: $extractedExportFile
×
110
            );
×
111
        } catch (ArchiveExtractionFailedException $e) {
×
112
            Log::warning('Archive extraction failed', [
×
113
                'user_id'          => $this->userId,
×
114
                'messenger_type'   => $this->messengerType,
×
115
                'export_file_path' => $this->exportFileStoredPath,
×
116
                'reason'           => $e->getMessage(),
×
117
            ]);
×
118
        } catch (QueryException $e) {
×
119
            Log::error('Import failed due to database query error', [
×
120
                'user_id'          => $this->userId,
×
121
                'messenger_type'   => $this->messengerType,
×
122
                'export_file_path' => $this->exportFileStoredPath,
×
123
                'strategy'         => $this->strategy->getName(),
×
124
                'error'            => $e->getMessage(),
×
125
            ]);
×
126

127
            $shouldDeleteSource = false;
×
128
            throw $e;
×
129
        } finally {
130
            /**
131
             * Delete temporary archive/export file
132
             */
133
            if ($shouldDeleteSource && Storage::exists($this->exportFileStoredPath)) {
×
134
                Storage::delete($this->exportFileStoredPath);
×
135
            }
136

137
            /**
138
             * Delete extraction directory
139
             */
140
            if (isset($extractedDir) && Storage::exists($extractedDir)) {
×
141
                Storage::deleteDirectory($extractedDir);
×
142
            }
143
        }
144
    }
145

146
    /**
147
     * Fallback for "Import to selected conversation" option when archive has additional media files that should be
148
     * merged into selected conversation
149
     *
150
     * @param ImportArchiveExtractorFactory $archiveExtractorsFactory
151
     * @param ParserRegistry                $parserRegistry
152
     * @param ImportedMediaResolverService  $importedMediaResolverService
153
     * @param MediaStorageInterface         $mediaStorage
154
     *
155
     * @return void
156
     */
157
    private function runMediaOnlyFallback(
158
        ImportArchiveExtractorFactory $archiveExtractorsFactory,
159
        ParserRegistry                $parserRegistry,
160
        ImportedMediaResolverService  $importedMediaResolverService,
161
        MediaStorageInterface         $mediaStorage
162
    ): void {
163
        /**
164
         * This fallback is only for "Import to selected conversation" scenario
165
         */
166
        if (!$this->strategy instanceof ImportOnlyMediaFilesStrategyInterface) {
×
167
            return;
×
168
        }
169

170
        $targetConversationId = $this->strategy->getImportMode()?->getTargetConversationId();
×
171
        if (!isset($targetConversationId)) {
×
172
            return;
×
173
        }
174

175
        $mediaUploadJob = new ProcessConversationMediaUpload(
×
176
            userId: $this->userId,
×
177
            conversationId: $targetConversationId,
×
178
            path: $this->exportFileStoredPath
×
179
        );
×
180

181
        $mediaUploadJob->handle(
×
182
            parserRegistry: $parserRegistry,
×
NEW
183
            importedMediaResolverService: $importedMediaResolverService,
×
NEW
184
            mediaStorage: $mediaStorage,
×
185
            archiveExtractorsFactory: $archiveExtractorsFactory
×
186
        );
×
187
    }
188
}
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