@bytebrand/export-cars 1.26.5 → 1.26.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bytebrand/export-cars",
3
- "version": "1.26.5",
3
+ "version": "1.26.7",
4
4
  "description": "",
5
5
  "author": "bytebrand",
6
6
  "license": "UNLICENSED",
@@ -8,6 +8,11 @@ import { FbXmlBuilder } from '../formats/fb-xml.builder';
8
8
  import { ModuleRef } from '@nestjs/core';
9
9
  import { FbTransformer } from '../transformers/fb.transformer';
10
10
  import { FbXmlItem } from '../interfaces';
11
+ import { META_PUSH_PRIORITY_ALLOWED_VALUES, META_PUSH_PRIORITY_STANDARD } from '../constants/fb.constants';
12
+
13
+ // dealer_priority_1 first, then dealer_priority_2, then everyone else (standard/unlisted) —
14
+ // mirrors the CSV's own priority tiers (see MappingService.getMetaPushPriorityMapping).
15
+ const PRIORITY_ORDER = [...META_PUSH_PRIORITY_ALLOWED_VALUES, META_PUSH_PRIORITY_STANDARD];
11
16
 
12
17
  @Injectable()
13
18
  export class FbExporter implements Exporter {
@@ -29,6 +34,12 @@ export class FbExporter implements Exporter {
29
34
 
30
35
  const fbTransformer = await this.moduleRef.create(FbTransformer);
31
36
 
37
+ // Buffered (not streamed straight to XML) so the whole feed can be sorted by dealer
38
+ // priority before writing — see the sort below. FbXmlItem doesn't declare custom_label_4
39
+ // (that interface predates ADV-159/161); the transformer always sets it, so extend locally
40
+ // rather than touching the shared interface other export types also cast through.
41
+ const cars: (FbXmlItem & { custom_label_4: string })[] = [];
42
+
32
43
  await this.loaderService.process(async (chunk: FsChunkInterface) => {
33
44
  const chunkData = chunk.data.filter(car => this.carService.filterCar(car, this.rulesService.rules));
34
45
  chunk.data = null;
@@ -42,15 +53,24 @@ export class FbExporter implements Exporter {
42
53
  console.timeEnd(`Transform ${data.car._id}`);
43
54
 
44
55
  if (!car) {
45
- console.log(`Skip car ${data.car._id} — no real images, or dealer ${ownerId} not in the priority list`);
56
+ console.log(`Skip car ${data.car._id} — no real images`);
46
57
  continue;
47
58
  }
48
59
 
49
- await fbXmlBuilder.addItem(car as FbXmlItem);
60
+ cars.push(car as FbXmlItem & { custom_label_4: string });
50
61
  this.carService.counter.countCar(data.images);
51
62
  }
52
63
  });
53
64
 
65
+ // ADV-161-follow-up: order by the meta push priority sheet (metaPushPriorityCsvUrl)
66
+ // instead of filtering non-listed dealers out — dealer_priority_1 first, then
67
+ // dealer_priority_2, then everyone else, in whatever order they were transformed.
68
+ cars.sort((a, b) => PRIORITY_ORDER.indexOf(a.custom_label_4) - PRIORITY_ORDER.indexOf(b.custom_label_4));
69
+
70
+ for (const car of cars) {
71
+ await fbXmlBuilder.addItem(car);
72
+ }
73
+
54
74
  await fbXmlBuilder.buildEnd();
55
75
  console.log(this.carService.counter.getCountedCars());
56
76
  }
@@ -85,8 +85,6 @@ export class ImagesService {
85
85
  'https://images.auto.de/carimage/noimage/pFyfBsqBHEso/large-cached.jpeg',
86
86
  ];
87
87
 
88
- private hasLoggedMarketingImageDebug = false;
89
-
90
88
  constructor(private readonly configService: ConfigService, private readonly userService: UserService) { }
91
89
 
92
90
  async getImages(car: DataSchemes.Car, images: ImboImageResponseInterface[], imageType: ImageType, ignoreDefImages: boolean): Promise<Array<string>> {
@@ -191,27 +189,17 @@ export class ImagesService {
191
189
  return ignoreDefImages ? [] : [...this.DEFAULT_IMAGES];
192
190
  }
193
191
 
194
- // marketingImageUrl (config.ts environmentConfig[env].imageUrl) points at /cache_test on
195
- // dev — a leftover from the old "fb" channel, not where image-generator publishes meta_aia
196
- // composites (always under /generated_images on both dev and prod). Build the meta_aia base
197
- // from the plain image host instead of trusting that per-env path.
192
+ // marketingImageUrl AND config.imageUrl (FbConfig overrides the latter to the same
193
+ // per-env value right after super()) both point at the old "fb" channel's base path
194
+ // (/cache_test on dev, already /generated_images on prod) — not where image-generator
195
+ // publishes meta_aia composites. baseImageUrl is the one field neither Config nor FbConfig
196
+ // ever suffixes with a path, so it's the only safe base to build /generated_images on.
198
197
  const imageUrl = config.channelCode === 'meta_aia'
199
- ? `${this.configService.config.imageUrl}/generated_images`
198
+ ? `${this.configService.config.baseImageUrl}/generated_images`
200
199
  : this.configService.config.marketingImageUrl;
201
200
 
202
201
  const marketingImageConfigs = config.marketingImages.map(i => ({ marketingImageCodes: i.imageCodes }));
203
202
 
204
- if (this.configService.config.exportType === ExportType.FB && !this.hasLoggedMarketingImageDebug) {
205
- this.hasLoggedMarketingImageDebug = true;
206
- console.log(
207
- 'getMarketingImages debug —',
208
- 'config.id:', config.id,
209
- 'config.channelCode:', config.channelCode,
210
- 'requested imageCodes per slot:', JSON.stringify(marketingImageConfigs.map(c => c.marketingImageCodes)),
211
- 'photo codes available on this car:', JSON.stringify(Object.keys(transformedImages)),
212
- );
213
- }
214
-
215
203
  const imageWithUrls: Partial<ImboImageResponseInterface & { url: string }>[] = [];
216
204
  for (let i = 0; i < marketingImageConfigs.length; i++) {
217
205
  const marketingImage = marketingImageConfigs[i];
@@ -219,15 +207,9 @@ export class ImagesService {
219
207
  const imageCode = marketingImage.marketingImageCodes[i];
220
208
  const imageId = transformedImages[imageCode];
221
209
  if (imageId) {
222
- const url = this.getImagePathWithExtension(imageUrl, carId, imageId, config);
223
- // meta_aia cards are composited async and may not exist yet for a given car —
224
- // verify the file is real before publishing the link, not just the placeholder.
225
- if (config.channelCode === 'meta_aia' && !(await this.imageExists(url))) {
226
- break;
227
- }
228
210
  imageWithUrls.push({
229
211
  imageCodePredicted: imageCode,
230
- url,
212
+ url: this.getImagePathWithExtension(imageUrl, carId, imageId, config),
231
213
  });
232
214
  break;
233
215
  }
@@ -244,22 +226,6 @@ export class ImagesService {
244
226
  return imageWithUrls.map(i => i.url);
245
227
  }
246
228
 
247
- // Imbo serves a 400x300 JPEG placeholder (measured 7996 bytes on both auto.de and
248
- // autode-dev.de) with a 404 status when a transformation isn't ready yet. nginx in front of
249
- // Imbo has been seen caching that placeholder body regardless of status code (see
250
- // nginx-image-cache-negative-caching), so check Content-Length rather than trusting the
251
- // HTTP status alone.
252
- private readonly PLACEHOLDER_MAX_BYTES = 8000;
253
-
254
- private async imageExists(url: string): Promise<boolean> {
255
- try {
256
- const { status, headers } = await axios.head(url, { timeout: 5000 });
257
- return status === 200 && Number(headers['content-length'] ?? 0) > this.PLACEHOLDER_MAX_BYTES;
258
- } catch {
259
- return false;
260
- }
261
- }
262
-
263
229
  private transformImagesArrayIntoObject(images: ImboImageResponseInterface[]): { [imageCode: string]: string } {
264
230
  return images.reduce((result, image) => {
265
231
  const imageCode = image?.imageCodePredicted?.split('-')[0];
@@ -32,7 +32,7 @@ import { ConfigService, ImagesService, MappingService } from '../services';
32
32
  import { FbEquipment } from './fb-equipment.transformer';
33
33
  import { isValidVin } from '../utils';
34
34
  import { UserService } from 'src/services';
35
- import { Environment, ImageType } from '../enums';
35
+ import { ImageType } from '../enums';
36
36
  import {
37
37
  DEFAULT_ADDR1,
38
38
  DEFAULT_CITY,
@@ -84,17 +84,6 @@ export class FbTransformer {
84
84
  const autodeId = toString(get(car, '_id', ''));
85
85
  const year = this.getYear(car);
86
86
  const ownerId = get(car, 'metaData.ownerId', null);
87
-
88
- // ADV-161-follow-up: on prod, only publish dealers listed in the meta push priority sheet
89
- // (metaPushPriorityCsvUrl). Dev dealer ids don't match that prod sheet, so this stays a
90
- // no-op there — every car passes through, same as before this filter existed.
91
- if (this.configService.config.environment === Environment.PRODUCTION) {
92
- const priorities = await this.mappingService.getMetaPushPriorityMapping();
93
- if (!priorities.some(priority => priority.dealer_id === ownerId)) {
94
- return null;
95
- }
96
- }
97
-
98
87
  const dealerInfo = this.getDealerItems(car);
99
88
  // console.time(`Address ${car._id}`);
100
89
  const address = await this.getAddress(car, owner);