|
23 | 23 | using Microsoft.EntityFrameworkCore; |
24 | 24 | using Microsoft.Extensions.Logging; |
25 | 25 | using Microsoft.Extensions.Options; |
| 26 | +using Sentry; |
26 | 27 | using File = System.IO.File; |
27 | 28 |
|
28 | 29 | namespace Eurofurence.App.Server.Services.Dealers |
@@ -198,94 +199,107 @@ public async Task RunImportAsync(CancellationToken cancellationToken = default) |
198 | 199 | } |
199 | 200 |
|
200 | 201 | var dealerPackagePath = Path.Combine(_globalOptions.WorkingDirectory, "dealers.zip"); |
201 | | - var newDealersExportDownloaded = await _dealerApiClient.DownloadDealersExportAsync(dealerPackagePath); |
202 | 202 |
|
203 | | - if (!newDealersExportDownloaded) |
| 203 | + try |
204 | 204 | { |
205 | | - _logger.LogError(LogEvents.Import, $"Error downloading the dealers export csv."); |
| 205 | + await _dealerApiClient.DownloadDealersExportAsync(dealerPackagePath); |
| 206 | + } |
| 207 | + catch (Exception ex) |
| 208 | + { |
| 209 | + SentrySdk.CaptureException(ex); |
| 210 | + _logger.LogError(LogEvents.Import, "Failed to download dealer export data: {exception}", ex.Message); |
206 | 211 | return; |
207 | 212 | } |
208 | 213 |
|
209 | 214 | var importRecords = new List<DealerRecord>(); |
210 | 215 |
|
211 | | - await using (var fileStream = File.OpenRead(dealerPackagePath)) |
212 | | - using (var archive = new ZipArchive(fileStream)) |
| 216 | + try |
213 | 217 | { |
214 | | - var csvEntry = |
215 | | - archive.Entries.Single(a => a.Name.EndsWith(".csv", StringComparison.InvariantCultureIgnoreCase)); |
216 | | - |
217 | | - TextReader reader = new StreamReader(csvEntry.Open(), true); |
218 | | - |
219 | | - var badData = new List<string>(); |
220 | | - |
221 | | - var csvConfiguration = new CsvConfiguration(CultureInfo.InvariantCulture) |
| 218 | + await using (var fileStream = File.OpenRead(dealerPackagePath)) |
| 219 | + using (var archive = new ZipArchive(fileStream)) |
222 | 220 | { |
223 | | - Delimiter = ";", |
224 | | - HasHeaderRecord = true, |
225 | | - TrimOptions = TrimOptions.Trim, |
226 | | - NewLine = "\n", |
227 | | - BadDataFound = arg => badData.Add(arg.Context.Parser.RawRecord) |
228 | | - }; |
| 221 | + var csvEntry = |
| 222 | + archive.Entries.Single(a => a.Name.EndsWith(".csv", StringComparison.InvariantCultureIgnoreCase)); |
229 | 223 |
|
230 | | - var csvReader = new CsvReader(reader, csvConfiguration); |
231 | | - csvReader.Context.RegisterClassMap<DealerImportRowClassMap>(); |
232 | | - var csvRecords = csvReader.GetRecords<DealerImportRow>().ToList(); |
| 224 | + TextReader reader = new StreamReader(csvEntry.Open(), true); |
233 | 225 |
|
234 | | - _logger.LogDebug(LogEvents.Import, $"Parsed {csvRecords.Count} records from CSV"); |
| 226 | + var badData = new List<string>(); |
235 | 227 |
|
236 | | - for (var i = 0; i < csvRecords.Count; i++) |
237 | | - { |
238 | | - var dealerRecord = new DealerRecord |
| 228 | + var csvConfiguration = new CsvConfiguration(CultureInfo.InvariantCulture) |
239 | 229 | { |
240 | | - Id = csvRecords[i].Id, |
241 | | - AboutTheArtistText = csvRecords[i].AboutTheArtist.Trim(), |
242 | | - AboutTheArtText = csvRecords[i].AboutTheArt.Trim(), |
243 | | - ArtPreviewCaption = csvRecords[i].ArtPreviewCaption.Trim(), |
244 | | - DisplayName = csvRecords[i].DisplayName.Trim(), |
245 | | - ShortDescription = csvRecords[i].ShortDescription.Trim(), |
246 | | - Merchandise = csvRecords[i].Merchandise.Trim(), |
247 | | - AttendsOnThursday = !string.IsNullOrWhiteSpace(csvRecords[i].AttendsThu), |
248 | | - AttendsOnFriday = !string.IsNullOrWhiteSpace(csvRecords[i].AttendsFri), |
249 | | - AttendsOnSaturday = !string.IsNullOrWhiteSpace(csvRecords[i].AttendsSat), |
250 | | - TelegramHandle = csvRecords[i].Telegram.Trim(), |
251 | | - TwitterHandle = csvRecords[i].Twitter.Trim(), |
252 | | - DiscordHandle = csvRecords[i].Discord.Trim(), |
253 | | - MastodonHandle = csvRecords[i].Mastodon.Trim(), |
254 | | - BlueskyHandle = csvRecords[i].Bluesky.Trim(), |
255 | | - IsAfterDark = !string.IsNullOrWhiteSpace(csvRecords[i].AfterDark), |
256 | | - Keywords = csvRecords[i].GetKeywords(), |
257 | | - Categories = csvRecords[i].GetCategories() |
| 230 | + Delimiter = ";", |
| 231 | + HasHeaderRecord = true, |
| 232 | + TrimOptions = TrimOptions.Trim, |
| 233 | + NewLine = "\n", |
| 234 | + BadDataFound = arg => badData.Add(arg.Context.Parser.RawRecord) |
258 | 235 | }; |
259 | 236 |
|
260 | | - dealerRecord.ArtistImageId = await GetImageIdAsync( |
261 | | - archive, |
262 | | - $"artist_{csvRecords[i].Id}.", |
263 | | - $"dealer:artist:{csvRecords[i].Id}", |
264 | | - cancellationToken |
265 | | - ); |
266 | | - dealerRecord.ArtistThumbnailImageId = await GetImageIdAsync( |
267 | | - archive, |
268 | | - $"thumbnail_{csvRecords[i].Id}.", |
269 | | - $"dealer:thumbnail:{csvRecords[i].Id}", |
270 | | - cancellationToken |
271 | | - ); |
272 | | - dealerRecord.ArtPreviewImageId = await GetImageIdAsync(archive, |
273 | | - $"art_{csvRecords[i].Id}.", |
274 | | - $"dealer:art:{csvRecords[i].Id}", |
275 | | - cancellationToken |
276 | | - ); |
277 | | - |
278 | | - ImportLinks(dealerRecord, csvRecords[i].Website); |
279 | | - SanitizeFields(dealerRecord); |
280 | | - |
281 | | - importRecords.Add(dealerRecord); |
282 | | - } |
| 237 | + var csvReader = new CsvReader(reader, csvConfiguration); |
| 238 | + csvReader.Context.RegisterClassMap<DealerImportRowClassMap>(); |
| 239 | + var csvRecords = csvReader.GetRecords<DealerImportRow>().ToList(); |
283 | 240 |
|
284 | | - if (badData.Count > 0) |
285 | | - { |
286 | | - _logger.LogInformation($"Found {badData.Count} bad rows:\n{string.Join("\n", badData)}"); |
| 241 | + _logger.LogDebug(LogEvents.Import, $"Parsed {csvRecords.Count} records from CSV"); |
| 242 | + |
| 243 | + for (var i = 0; i < csvRecords.Count; i++) |
| 244 | + { |
| 245 | + var dealerRecord = new DealerRecord |
| 246 | + { |
| 247 | + Id = csvRecords[i].Id, |
| 248 | + AboutTheArtistText = csvRecords[i].AboutTheArtist.Trim(), |
| 249 | + AboutTheArtText = csvRecords[i].AboutTheArt.Trim(), |
| 250 | + ArtPreviewCaption = csvRecords[i].ArtPreviewCaption.Trim(), |
| 251 | + DisplayName = csvRecords[i].DisplayName.Trim(), |
| 252 | + ShortDescription = csvRecords[i].ShortDescription.Trim(), |
| 253 | + Merchandise = csvRecords[i].Merchandise.Trim(), |
| 254 | + AttendsOnThursday = !string.IsNullOrWhiteSpace(csvRecords[i].AttendsThu), |
| 255 | + AttendsOnFriday = !string.IsNullOrWhiteSpace(csvRecords[i].AttendsFri), |
| 256 | + AttendsOnSaturday = !string.IsNullOrWhiteSpace(csvRecords[i].AttendsSat), |
| 257 | + TelegramHandle = csvRecords[i].Telegram.Trim(), |
| 258 | + TwitterHandle = csvRecords[i].Twitter.Trim(), |
| 259 | + DiscordHandle = csvRecords[i].Discord.Trim(), |
| 260 | + MastodonHandle = csvRecords[i].Mastodon.Trim(), |
| 261 | + BlueskyHandle = csvRecords[i].Bluesky.Trim(), |
| 262 | + IsAfterDark = !string.IsNullOrWhiteSpace(csvRecords[i].AfterDark), |
| 263 | + Keywords = csvRecords[i].GetKeywords(), |
| 264 | + Categories = csvRecords[i].GetCategories(), |
| 265 | + ArtistImageId = await GetImageIdAsync( |
| 266 | + archive, |
| 267 | + $"artist_{csvRecords[i].Id}.", |
| 268 | + $"dealer:artist:{csvRecords[i].Id}", |
| 269 | + cancellationToken |
| 270 | + ), |
| 271 | + ArtistThumbnailImageId = await GetImageIdAsync( |
| 272 | + archive, |
| 273 | + $"thumbnail_{csvRecords[i].Id}.", |
| 274 | + $"dealer:thumbnail:{csvRecords[i].Id}", |
| 275 | + cancellationToken |
| 276 | + ), |
| 277 | + ArtPreviewImageId = await GetImageIdAsync( |
| 278 | + archive, |
| 279 | + $"art_{csvRecords[i].Id}.", |
| 280 | + $"dealer:art:{csvRecords[i].Id}", |
| 281 | + cancellationToken |
| 282 | + ) |
| 283 | + }; |
| 284 | + |
| 285 | + ImportLinks(dealerRecord, csvRecords[i].Website); |
| 286 | + SanitizeFields(dealerRecord); |
| 287 | + |
| 288 | + importRecords.Add(dealerRecord); |
| 289 | + } |
| 290 | + |
| 291 | + if (badData.Count > 0) |
| 292 | + { |
| 293 | + _logger.LogInformation($"Found {badData.Count} bad rows:\n{string.Join("\n", badData)}"); |
| 294 | + } |
287 | 295 | } |
288 | 296 | } |
| 297 | + catch (Exception ex) |
| 298 | + { |
| 299 | + SentrySdk.CaptureException(ex); |
| 300 | + _logger.LogError(LogEvents.Import, "Failed to process dealer export: {exception}", ex.Message); |
| 301 | + return; |
| 302 | + } |
289 | 303 |
|
290 | 304 | var existingRecords = FindAll(); |
291 | 305 |
|
@@ -322,7 +336,7 @@ public async Task RunImportAsync(CancellationToken cancellationToken = default) |
322 | 336 |
|
323 | 337 | File.Delete(dealerPackagePath); |
324 | 338 | _logger.LogInformation(LogEvents.Import, |
325 | | - $"Dealers import with {diff.Count(p => p.Action == ActionEnum.Add)} addition(s), {diff.Count(p => p.Action == ActionEnum.Update)} update(s) and {diff.Count(p => p.Action == ActionEnum.Delete)} deletion(s) finished successfully with {diff.Count(a => a.Action == ActionEnum.NotModified)} unmodified."); |
| 339 | + $"Dealers import with {diff.Count(p => p.Action == ActionEnum.Add)} addition(s), {diff.Count(p => p.Action == ActionEnum.Update)} update(s) and {diff.Count(p => p.Action == ActionEnum.Delete)} deletion(s) finished successfully with {diff.Count(a => a.Action == ActionEnum.NotModified)} unmodified."); |
326 | 340 | } |
327 | 341 | finally |
328 | 342 | { |
@@ -382,10 +396,11 @@ private void ImportLinks(DealerRecord dealerRecord, string websiteUrls) |
382 | 396 |
|
383 | 397 | var sanitizedParts = websiteUrls |
384 | 398 | .Replace(" / ", ";") |
385 | | - .Split(new[] |
386 | | - { |
387 | | - ' ', ',', ';' |
388 | | - }, StringSplitOptions.RemoveEmptyEntries); |
| 399 | + .Split( |
| 400 | + [ |
| 401 | + ' ', ',', ';' |
| 402 | + ], |
| 403 | + StringSplitOptions.RemoveEmptyEntries); |
389 | 404 |
|
390 | 405 | foreach (var part in sanitizedParts) |
391 | 406 | { |
|
0 commit comments