@bytebrand/export-cars 1.24.0 → 1.25.0
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/dist/exporters/fb.exporter.js +4 -0
- package/dist/exporters/fb.exporter.js.map +1 -1
- package/dist/formats/facebook-shop.builder.js +6 -0
- package/dist/formats/facebook-shop.builder.js.map +1 -1
- package/dist/formats/fb-xml.builder.js +9 -2
- package/dist/formats/fb-xml.builder.js.map +1 -1
- package/dist/interfaces/export-item.interface.d.ts +3 -3
- package/dist/transformers/facebook-shop.transformer.d.ts +1 -0
- package/dist/transformers/facebook-shop.transformer.js +9 -7
- package/dist/transformers/facebook-shop.transformer.js.map +1 -1
- package/dist/transformers/fb.transformer.js +14 -8
- package/dist/transformers/fb.transformer.js.map +1 -1
- package/dist/transformers/google.transformer.d.ts +4 -2
- package/dist/transformers/google.transformer.js +15 -2
- package/dist/transformers/google.transformer.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/exporters/fb.exporter.ts +5 -0
- package/src/formats/facebook-shop.builder.ts +6 -0
- package/src/formats/fb-xml.builder.ts +11 -2
- package/src/interfaces/export-item.interface.ts +3 -3
- package/src/transformers/facebook-shop.transformer.ts +10 -7
- package/src/transformers/fb.transformer.ts +23 -10
- package/src/transformers/google.transformer.ts +18 -3
package/package.json
CHANGED
|
@@ -41,6 +41,11 @@ export class FbExporter implements Exporter {
|
|
|
41
41
|
const car = await fbTransformer.transform({ owner, ...data });
|
|
42
42
|
console.timeEnd(`Transform ${data.car._id}`);
|
|
43
43
|
|
|
44
|
+
if (!car) {
|
|
45
|
+
console.log(`Skip car ${data.car._id} because it has no real images`);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
|
|
44
49
|
await fbXmlBuilder.addItem(car as FbXmlItem);
|
|
45
50
|
this.carService.counter.countCar(data.images);
|
|
46
51
|
}
|
|
@@ -17,6 +17,12 @@ export class FacebookShopXmlBuilder extends XMLBuilderBase {
|
|
|
17
17
|
const tags = Object.keys(car);
|
|
18
18
|
|
|
19
19
|
for (const tag of tags) {
|
|
20
|
+
if (tag === 'image' && Array.isArray(car[tag])) {
|
|
21
|
+
item += (car[tag] as unknown as string[])
|
|
22
|
+
.map((url: string) => `<image><url>${escape(url)}</url></image>\n`)
|
|
23
|
+
.join('');
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
20
26
|
item += this.buildTagElement(tag, car[tag]);
|
|
21
27
|
}
|
|
22
28
|
item += '</item>';
|
|
@@ -16,8 +16,17 @@ export class FbXmlBuilder extends XMLBuilderBase {
|
|
|
16
16
|
if (key === 'description') {
|
|
17
17
|
return this.getDescriptionElement(key, data);
|
|
18
18
|
}
|
|
19
|
-
if (key === '
|
|
20
|
-
return data.map((
|
|
19
|
+
if (key === 'image' && Array.isArray(data)) {
|
|
20
|
+
return this.getElementsArray(key, data.map((url: string) => ({ url })));
|
|
21
|
+
}
|
|
22
|
+
if (key === 'vehicle_specifications' && Array.isArray(data)) {
|
|
23
|
+
// getElement() treats falsy values as "missing" — stringify so a legitimate 0
|
|
24
|
+
// (e.g. CO2_emissions for an EV) still renders instead of an empty <value>.
|
|
25
|
+
const stringified = data.map((spec: { type: string; units: string; value: number }) => ({
|
|
26
|
+
...spec,
|
|
27
|
+
value: String(spec.value),
|
|
28
|
+
}));
|
|
29
|
+
return this.getElementsArray(key, stringified);
|
|
21
30
|
}
|
|
22
31
|
if (Array.isArray(data)) {
|
|
23
32
|
return this.getElementsArray(key, data);
|
|
@@ -207,6 +207,7 @@ export interface GoogleXmlItem {
|
|
|
207
207
|
identifier_exists: string,
|
|
208
208
|
ads_redirect?: string;
|
|
209
209
|
"certified_pre-owned"?: boolean;
|
|
210
|
+
user_id?: string;
|
|
210
211
|
custom_label_0?: string;
|
|
211
212
|
custom_label_1?: string,
|
|
212
213
|
custom_label_2?: string,
|
|
@@ -351,11 +352,10 @@ export interface FacebookShopItem {
|
|
|
351
352
|
title: FacebookShopTag;
|
|
352
353
|
description: FacebookShopTag;
|
|
353
354
|
availability: FacebookShopTag;
|
|
354
|
-
|
|
355
|
+
state_of_vehicle: FacebookShopTag;
|
|
355
356
|
price: FacebookShopTag;
|
|
356
357
|
link: FacebookShopTag;
|
|
357
|
-
|
|
358
|
-
additional_image_link: FacebookShopTag;
|
|
358
|
+
image: string[];
|
|
359
359
|
make: FacebookShopTag;
|
|
360
360
|
model: FacebookShopTag;
|
|
361
361
|
year: FacebookShopTag;
|
|
@@ -78,13 +78,12 @@ export class FacebookShopTransformer {
|
|
|
78
78
|
|
|
79
79
|
const base = {
|
|
80
80
|
vehicle_id: this.tag(vehicleId, true),
|
|
81
|
-
availability: this.tag(
|
|
82
|
-
|
|
81
|
+
availability: this.tag(this.getAvailability(car), false),
|
|
82
|
+
state_of_vehicle: this.tag(this.getCondition(car), false),
|
|
83
83
|
price: this.tag(`${price} EUR`, true),
|
|
84
84
|
sale_price: this.tag(`${price} EUR`, true),
|
|
85
85
|
link: this.tag(this.getCarLink(car), true),
|
|
86
|
-
|
|
87
|
-
additional_image_link: this.tag(imageLinks[0], false),
|
|
86
|
+
image: imageLinks.slice(0, 2), // main + 2nd image, per ticket
|
|
88
87
|
make: this.tag(make, false),
|
|
89
88
|
model: this.tag(model, false),
|
|
90
89
|
year: this.tag(this.getYear(car), false),
|
|
@@ -252,14 +251,18 @@ export class FacebookShopTransformer {
|
|
|
252
251
|
|
|
253
252
|
private getCondition(car: DataSchemes.Car): string {
|
|
254
253
|
const available = {
|
|
255
|
-
selector_condition_new: '
|
|
256
|
-
selector_condition_used: '
|
|
257
|
-
selector_unknown: '
|
|
254
|
+
selector_condition_new: 'New',
|
|
255
|
+
selector_condition_used: 'Used',
|
|
256
|
+
selector_unknown: 'Used',
|
|
258
257
|
};
|
|
259
258
|
const condition = get(car, 'mainData.condition', 'selector_unknown');
|
|
260
259
|
return get(available, condition);
|
|
261
260
|
}
|
|
262
261
|
|
|
262
|
+
private getAvailability(car: DataSchemes.Car): string {
|
|
263
|
+
return get(car, 'state.isOnline', false) ? 'available' : 'not_available';
|
|
264
|
+
}
|
|
265
|
+
|
|
263
266
|
private getCarLink(car: DataSchemes.Car): string {
|
|
264
267
|
return `https://${this.configService.config.domain}/search/vehicle/${car._id}`;
|
|
265
268
|
}
|
|
@@ -44,6 +44,9 @@ import {
|
|
|
44
44
|
TIMESTAMP_FIRST_YEAR,
|
|
45
45
|
} from '../constants/fb.constants';
|
|
46
46
|
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
48
|
+
const vinGenerator = require('vin-generator');
|
|
49
|
+
|
|
47
50
|
@Injectable()
|
|
48
51
|
export class FbTransformer {
|
|
49
52
|
private translator: TranslatorExtended;
|
|
@@ -71,6 +74,13 @@ export class FbTransformer {
|
|
|
71
74
|
images: ImboImageResponseInterface[];
|
|
72
75
|
owner: unknown;
|
|
73
76
|
}): Promise<unknown> {
|
|
77
|
+
// ignoreDefImages=true: cars with no real photos must be excluded, not padded with
|
|
78
|
+
// placeholders (matches facebook-shop.transformer.ts's AMA feed policy).
|
|
79
|
+
const image_links = await this.imagesService.getImages(car, images, ImageType.marketing, true);
|
|
80
|
+
if (!image_links.length) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
74
84
|
const autodeId = toString(get(car, '_id', ''));
|
|
75
85
|
const year = this.getYear(car);
|
|
76
86
|
const ownerId = get(car, 'metaData.ownerId', null);
|
|
@@ -78,9 +88,6 @@ export class FbTransformer {
|
|
|
78
88
|
// console.time(`Address ${car._id}`);
|
|
79
89
|
const address = await this.getAddress(car, owner);
|
|
80
90
|
// console.timeEnd(`Address ${car._id}`);
|
|
81
|
-
// console.time(`Images ${car._id}`);
|
|
82
|
-
const image_links = await this.imagesService.getImages(car, images, ImageType.marketing, false);
|
|
83
|
-
// console.timeEnd(`Images ${car._id}`);
|
|
84
91
|
|
|
85
92
|
return {
|
|
86
93
|
fb_page_id: '176306386132',
|
|
@@ -99,8 +106,7 @@ export class FbTransformer {
|
|
|
99
106
|
make: toString(get(car, 'mainData.make', '')),
|
|
100
107
|
model: toString(get(car, 'mainData.model', '')),
|
|
101
108
|
trim: this.getTrim(car),
|
|
102
|
-
|
|
103
|
-
additional_image_link: image_links.slice(1, 21), // Meta allows max 20 additional images
|
|
109
|
+
image: image_links.slice(0, 20), // Meta caps the whole image array at 20 items
|
|
104
110
|
year: toString(year),
|
|
105
111
|
mileage: {
|
|
106
112
|
value: this.getMileage(car),
|
|
@@ -114,7 +120,7 @@ export class FbTransformer {
|
|
|
114
120
|
price: this.getPrice(car),
|
|
115
121
|
exterior_color: this.getExteriorColor(car),
|
|
116
122
|
interior_color: this.getInteriorColor(car),
|
|
117
|
-
|
|
123
|
+
state_of_vehicle: this.mapCondition(car),
|
|
118
124
|
energy_efficiency_class_eu: this.getEnergyEfficiencyClass(car),
|
|
119
125
|
vehicle_specifications: this.getVehicleSpecifications(car),
|
|
120
126
|
};
|
|
@@ -218,6 +224,13 @@ export class FbTransformer {
|
|
|
218
224
|
vin = get(car, 'marketing.fakeVIN', '');
|
|
219
225
|
}
|
|
220
226
|
|
|
227
|
+
// Meta requires a vin on every listing. Real + fake VIN are both absent for a small
|
|
228
|
+
// share of cars (marketing.fakeVIN is populated by a different upstream service) —
|
|
229
|
+
// same generated-VIN fallback already used by mobile.transformer.ts/santander.transformer.ts.
|
|
230
|
+
if (!isValidVin(vin)) {
|
|
231
|
+
vin = vinGenerator.generateVin();
|
|
232
|
+
}
|
|
233
|
+
|
|
221
234
|
return vin;
|
|
222
235
|
}
|
|
223
236
|
|
|
@@ -381,16 +394,16 @@ export class FbTransformer {
|
|
|
381
394
|
|
|
382
395
|
private mapCondition(car: DataSchemes.Car): string {
|
|
383
396
|
const available = {
|
|
384
|
-
selector_condition_new: '
|
|
385
|
-
selector_condition_used: '
|
|
386
|
-
selector_unknown: '
|
|
397
|
+
selector_condition_new: 'New',
|
|
398
|
+
selector_condition_used: 'Used',
|
|
399
|
+
selector_unknown: 'Used',
|
|
387
400
|
};
|
|
388
401
|
const condition = get(car, 'mainData.condition', 'selector_unknown');
|
|
389
402
|
return get(available, condition);
|
|
390
403
|
}
|
|
391
404
|
|
|
392
405
|
private mapAvailability(car: DataSchemes.Car): string {
|
|
393
|
-
return get(car, 'state.isOnline', false) ? '
|
|
406
|
+
return get(car, 'state.isOnline', false) ? 'available' : 'not_available';
|
|
394
407
|
}
|
|
395
408
|
|
|
396
409
|
private getEnergyEfficiencyClass(car: DataSchemes.Car): string {
|
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
import { escape, get, isNumber, join, round, toSafeInteger, toString, trim, truncate } from 'lodash';
|
|
1
|
+
import { escape, get, includes, isNumber, join, round, toSafeInteger, toString, trim, truncate } from 'lodash';
|
|
2
2
|
import { CarDescriptionFactory, DescriptionTypeEnum } from '@bytebrand/car-description';
|
|
3
3
|
import { DataSchemes } from '@bytebrand/car-schema';
|
|
4
4
|
import { TranslatorExtended } from '@bytebrand/schema-tools';
|
|
5
5
|
import { de, deVehicleDetailsPage, deVehicleProps } from '@bytebrand/i18n-dictionaries';
|
|
6
6
|
import { ImboImageResponseInterface } from '@bytebrand/chunkable';
|
|
7
7
|
import { Injectable } from '@nestjs/common';
|
|
8
|
-
import { ConfigService, ImagesService, TransformerService, UserService } from '../services';
|
|
8
|
+
import { ConfigService, ImagesService, MappingService, TransformerService, UserService } from '../services';
|
|
9
9
|
import { GoogleXmlItem } from '../interfaces';
|
|
10
10
|
import { isValidVin } from '../utils';
|
|
11
|
+
import {
|
|
12
|
+
META_PUSH_PRIORITY_ALLOWED_VALUES,
|
|
13
|
+
META_PUSH_PRIORITY_STANDARD,
|
|
14
|
+
} from '../constants/fb.constants';
|
|
11
15
|
|
|
12
16
|
const GOOGLE_MAX_TRIM_LENGTH = 50;
|
|
13
17
|
const TIMESTAMP_FIRST_YEAR = 1970;
|
|
@@ -21,6 +25,7 @@ export class GoogleTransformer {
|
|
|
21
25
|
private configService: ConfigService,
|
|
22
26
|
private imageService: ImagesService,
|
|
23
27
|
private readonly transformService: TransformerService,
|
|
28
|
+
private readonly mappingService: MappingService,
|
|
24
29
|
) {
|
|
25
30
|
const dict = { ...de, ...deVehicleProps, ...deVehicleDetailsPage };
|
|
26
31
|
this.translator = new TranslatorExtended(dict);
|
|
@@ -50,6 +55,8 @@ export class GoogleTransformer {
|
|
|
50
55
|
...transformedImages.feat
|
|
51
56
|
]
|
|
52
57
|
|
|
58
|
+
const ownerId = get(car, 'metaData.ownerId', null);
|
|
59
|
+
|
|
53
60
|
const item: GoogleXmlItem = {
|
|
54
61
|
google_product_category: '916',
|
|
55
62
|
vehicle_fulfillment: {
|
|
@@ -92,11 +99,12 @@ export class GoogleTransformer {
|
|
|
92
99
|
description: this.processText((await this.getDescription(car))),
|
|
93
100
|
identifier_exists: 'yes',
|
|
94
101
|
availability: 'in_stock',
|
|
102
|
+
user_id: ownerId,
|
|
103
|
+
custom_label_4: await this.getMetaPushPriority(ownerId),
|
|
95
104
|
// custom_label_0: '',
|
|
96
105
|
// custom_label_1: '',
|
|
97
106
|
// custom_label_2: '',
|
|
98
107
|
// custom_label_3: '',
|
|
99
|
-
// custom_label_4: '',
|
|
100
108
|
|
|
101
109
|
// availability: this.getAvailability(car),
|
|
102
110
|
// sale_price: this.getPrice(car),
|
|
@@ -131,6 +139,13 @@ export class GoogleTransformer {
|
|
|
131
139
|
return item;
|
|
132
140
|
}
|
|
133
141
|
|
|
142
|
+
private async getMetaPushPriority(ownerId: string): Promise<string> {
|
|
143
|
+
const priorities = await this.mappingService.getMetaPushPriorityMapping();
|
|
144
|
+
const entry = priorities.find(priority => priority.dealer_id === ownerId);
|
|
145
|
+
const value = get(entry, 'meta_push_priority', '');
|
|
146
|
+
return includes(META_PUSH_PRIORITY_ALLOWED_VALUES, value) ? value : META_PUSH_PRIORITY_STANDARD;
|
|
147
|
+
}
|
|
148
|
+
|
|
134
149
|
private checkCarValidity(car: DataSchemes.Car, images: ImboImageResponseInterface[]) {
|
|
135
150
|
if (images.length === 0)
|
|
136
151
|
return false
|