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

LeTraceurSnorkLibrary / MessSaga / 23844646769

01 Apr 2026 10:42AM UTC coverage: 31.674% (+31.1%) from 0.549%
23844646769

push

github

web-flow
feat: media files import

431 of 801 new or added lines in 30 files covered. (53.81%)

4 existing lines in 4 files now uncovered.

439 of 1386 relevant lines covered (31.67%)

0.74 hits per line

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

0.0
/app/Services/ImportService.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace App\Services;
6

7
use App\Models\MediaAttachment;
8
use App\Models\MessengerAccount;
9
use App\Services\Import\Archives\DTO\ArchiveExtractionResult;
10
use App\Services\Import\DTO\PreparedMessageRowResult;
11
use App\Services\Import\MessageInsertService;
12
use App\Services\Import\MessagePreparationService;
13
use App\Services\Import\Strategies\ImportStrategyInterface;
14
use App\Services\Parsers\ParserRegistry;
15
use Illuminate\Database\QueryException;
16
use Illuminate\Support\Facades\DB;
17
use Illuminate\Support\Facades\Log;
18
use Illuminate\Support\Facades\Storage;
19
use InvalidArgumentException;
20
use RuntimeException;
21

22
class ImportService
23
{
24
    /**
25
     * @param ParserRegistry          $parserRegistry
26
     * @param MessagePreparationService $messagePreparationService
27
     * @param MessageInsertService      $messageInsertService
28
     */
29
    public function __construct(
30
        protected ParserRegistry          $parserRegistry,
31
        protected MessagePreparationService $messagePreparationService,
32
        protected MessageInsertService      $messageInsertService,
33
    ) {
34
    }
×
35

36
    /**
37
     * @param int                     $userId
38
     * @param string                  $messengerType
39
     * @param ImportStrategyInterface $strategy
40
     * @param ArchiveExtractionResult $extractedExportFile
41
     *
42
     * @throws QueryException
43
     * @return void
44
     */
45
    public function import(
46
        int                     $userId,
47
        string                  $messengerType,
48
        ImportStrategyInterface $strategy,
49
        ArchiveExtractionResult $extractedExportFile
50
    ): void {
NEW
51
        $exportFilePath = $extractedExportFile->getExportFileAbsolutePath();
×
NEW
52
        $mediaRootPath  = $extractedExportFile->getMediaRootPath();
×
53

NEW
54
        if ($exportFilePath === null) {
×
NEW
55
            return;
×
56
        }
57

58
        try {
NEW
59
            $parser               = $this->parserRegistry->get($messengerType);
×
NEW
60
            $importedConversation = $parser->parse($exportFilePath);
×
NEW
61
        } catch (RuntimeException|InvalidArgumentException $e) {
×
62
            Log::error('Import parsing failed', [
×
63
                'user_id'        => $userId,
×
64
                'messenger_type' => $messengerType,
×
NEW
65
                'path'           => $exportFilePath,
×
66
                'error'          => $e->getMessage(),
×
67
                'trace'          => $e->getTraceAsString(),
×
68
            ]);
×
69

70
            return;
×
71
        }
72

NEW
73
        if (!$importedConversation->hasConversation()) {
×
74
            Log::notice('Import skipped - no conversation data', [
×
75
                'user_id'        => $userId,
×
76
                'messenger_type' => $messengerType,
×
77
            ]);
×
78

79
            return;
×
80
        }
81

NEW
82
        $conversation = DB::transaction(function () use (
×
UNCOV
83
            $userId,
×
84
            $messengerType,
×
NEW
85
            $importedConversation,
×
NEW
86
            $strategy
×
87
        ) {
×
NEW
88
            $conversationData = $importedConversation->getConversationData();
×
89

90
            $account = MessengerAccount::firstOrCreate(
×
91
                [
×
92
                    'user_id' => $userId,
×
93
                    'type'    => $messengerType,
×
94
                ],
×
95
                [
×
96
                    'name' => $conversationData['account_name'] ?? ucfirst($messengerType),
×
97
                    'meta' => $conversationData['account_meta'] ?? [],
×
98
                ],
×
99
            );
×
100

NEW
101
            return $strategy->resolveConversation(
×
102
                account: $account,
×
103
                conversationData: $conversationData
×
104
            );
×
NEW
105
        });
×
106

NEW
107
        if (!$conversation) {
×
NEW
108
            Log::warning('Import aborted - no conversation target', [
×
NEW
109
                'mode'    => $strategy->getName(),
×
NEW
110
                'user_id' => $userId,
×
NEW
111
            ]);
×
112

NEW
113
            return;
×
114
        }
115

NEW
116
        $messagesRelation    = $parser->getMessagesRelation($conversation);
×
NEW
117
        $existingExternalIds = $messagesRelation
×
NEW
118
            ->whereNotNull('external_id')
×
NEW
119
            ->pluck('external_id')
×
NEW
120
            ->map(static fn($id): string => (string)$id)
×
NEW
121
            ->flip();
×
NEW
122
        $existingDedupHashes = $messagesRelation
×
NEW
123
            ->whereNotNull('dedup_hash')
×
NEW
124
            ->pluck('dedup_hash')
×
NEW
125
            ->map(static fn($hash): string => (string)$hash)
×
NEW
126
            ->flip();
×
127

NEW
128
        $preparedMessages  = [];
×
NEW
129
        $copiedMediaPaths  = [];
×
NEW
130
        $messageModelClass = $parser->getMessageModelClass();
×
NEW
131
        foreach ($importedConversation->getMessages() as $message) {
×
NEW
132
            $externalId = $this->messagePreparationService->normalizeExternalId($message['external_id'] ?? null);
×
NEW
133
            $dedupHash  = $this->messagePreparationService->buildDeduplicationHash($message);
×
134

NEW
135
            if ($externalId !== null && $existingExternalIds->has($externalId)) {
×
NEW
136
                continue;
×
137
            }
NEW
138
            if ($existingDedupHashes->has($dedupHash)) {
×
NEW
139
                continue;
×
140
            }
141

NEW
142
            if ($externalId !== null) {
×
NEW
143
                $existingExternalIds->put($externalId, true);
×
144
            }
NEW
145
            $existingDedupHashes->put($dedupHash, true);
×
146

NEW
147
            $attachmentStoredPath = $this->messagePreparationService->copyAttachmentForMessage(
×
NEW
148
                $mediaRootPath,
×
NEW
149
                $message,
×
NEW
150
                $conversation->id
×
NEW
151
            );
×
NEW
152
            if ($attachmentStoredPath !== null) {
×
NEW
153
                $copiedMediaPaths[$attachmentStoredPath] = true;
×
154
            }
155

NEW
156
            $message['dedup_hash'] = $dedupHash;
×
157

NEW
158
            $preparedMessages[] = $this->messagePreparationService->prepareMessageRowForInsert(
×
NEW
159
                $message,
×
NEW
160
                $conversation->id,
×
NEW
161
                $messageModelClass,
×
NEW
162
                $attachmentStoredPath,
×
NEW
163
            );
×
164
        }
165

NEW
166
        $importedCount = 0;
×
167
        try {
NEW
168
            DB::transaction(function () use (
×
NEW
169
                $messageModelClass,
×
NEW
170
                $preparedMessages,
×
NEW
171
                $conversation,
×
NEW
172
                &$importedCount,
×
NEW
173
                &$copiedMediaPaths
×
NEW
174
            ) {
×
175
                /**
176
                 * @var PreparedMessageRowResult $prepared
177
                 */
NEW
178
                foreach ($preparedMessages as $prepared) {
×
NEW
179
                    $msg = $this->messageInsertService->createMessageSafely($messageModelClass, $prepared->getRow());
×
NEW
180
                    if ($msg === null) {
×
NEW
181
                        $preparedMedia = $prepared->getMedia();
×
NEW
182
                        $preparedPath  = is_array($preparedMedia)
×
NEW
183
                            ? ($preparedMedia['stored_path'] ?? null)
×
NEW
184
                            : null;
×
NEW
185
                        if (is_string($preparedPath) && $preparedPath !== '' && Storage::exists($preparedPath)) {
×
NEW
186
                            Storage::delete($preparedPath);
×
NEW
187
                            unset($copiedMediaPaths[$preparedPath]);
×
188
                        }
189

NEW
190
                        continue;
×
191
                    }
192

NEW
193
                    $media = $prepared->getMedia();
×
NEW
194
                    if (isset($media)) {
×
NEW
195
                        $media = MediaAttachment::create(array_merge($media, [
×
NEW
196
                            'conversation_id' => $conversation->id,
×
NEW
197
                        ]));
×
NEW
198
                        $msg->update(['media_attachment_id' => $media->id]);
×
199
                    }
200

NEW
201
                    $importedCount++;
×
202
                }
NEW
203
            });
×
NEW
204
        } catch (QueryException $e) {
×
NEW
205
            foreach (array_keys($copiedMediaPaths) as $path) {
×
NEW
206
                if (Storage::exists($path)) {
×
NEW
207
                    Storage::delete($path);
×
208
                }
209
            }
210

NEW
211
            throw $e;
×
212
        }
213

NEW
214
        if ($importedCount > 0) {
×
NEW
215
            Log::info('Messages imported', [
×
NEW
216
                'conversation_id' => $conversation->id,
×
NEW
217
                'count'           => $importedCount,
×
NEW
218
            ]);
×
219
        } else {
NEW
220
            Log::info('No new messages to import', [
×
NEW
221
                'conversation_id' => $conversation->id,
×
NEW
222
            ]);
×
223
        }
224
    }
225
}
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