@bytebrand/export-cars 1.20.0 → 1.21.1
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/configs/config.d.ts +1 -0
- package/dist/configs/config.js +1 -0
- package/dist/configs/config.js.map +1 -1
- package/dist/configs/evium.config.js +1 -0
- package/dist/configs/evium.config.js.map +1 -1
- package/dist/exporters/mobile.exporter.d.ts +3 -2
- package/dist/exporters/mobile.exporter.js +10 -3
- package/dist/exporters/mobile.exporter.js.map +1 -1
- package/dist/services/images.service.d.ts +5 -2
- package/dist/services/images.service.js +30 -10
- package/dist/services/images.service.js.map +1 -1
- package/dist/transformers/mobile.transformer.js +2 -0
- package/dist/transformers/mobile.transformer.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +3 -2
- package/src/configs/config.ts +1 -0
- package/src/configs/evium.config.ts +1 -0
- package/src/exporters/mobile.exporter.ts +12 -7
- package/src/services/images.service.ts +42 -31
- package/src/transformers/mobile.transformer.ts +5 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bytebrand/export-cars",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.21.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"author": "bytebrand",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"@nestjs/cache-manager": "^2.1.0",
|
|
29
29
|
"@nestjs/common": "^10.2.7",
|
|
30
30
|
"@nestjs/core": "^10.2.7",
|
|
31
|
+
"axios": "^1.5.1",
|
|
31
32
|
"bottleneck": "^2.19.5",
|
|
32
33
|
"cache-manager": "^5.2.4",
|
|
33
34
|
"csv-write-stream": "^2.0.0",
|
|
@@ -61,5 +62,5 @@
|
|
|
61
62
|
"engines": {
|
|
62
63
|
"node": ">=14.0.0"
|
|
63
64
|
},
|
|
64
|
-
"gitHead": "
|
|
65
|
+
"gitHead": "ea5d40b0c028d75455818e481f87d74bd18f820c"
|
|
65
66
|
}
|
package/src/configs/config.ts
CHANGED
|
@@ -10,6 +10,7 @@ export class EviumConfig extends Config {
|
|
|
10
10
|
this.imageUrl = `https://images.${this.domain}/carimage`;
|
|
11
11
|
this.mappingSpreadsheetTitle = 'mobile_yescar';
|
|
12
12
|
this.imageType = ImageType.default;
|
|
13
|
+
this.downloadImage = true;
|
|
13
14
|
this.outputFiles = [
|
|
14
15
|
`${this.pathesConfig.localConvertedDirectory}/api_export_cars__${ExportType.EVIUM}.${FileType.CSV}`,
|
|
15
16
|
`${this.pathesConfig.localConvertedDirectory}/api_export_cars_${ExportType.EVIUM}.${FileType.ZIP}`,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { writeFile } from 'fs/promises';
|
|
2
|
-
import {
|
|
2
|
+
import { normalize, parse } from 'path';
|
|
3
3
|
|
|
4
4
|
import { Injectable } from '@nestjs/common';
|
|
5
5
|
import { ModuleRef } from '@nestjs/core';
|
|
@@ -8,10 +8,10 @@ import { CronicleUtils } from '@bytebrand/cronicle-helper';
|
|
|
8
8
|
import { FsChunkInterface } from '@bytebrand/chunkable';
|
|
9
9
|
import { Zipper } from '@bytebrand/zipper';
|
|
10
10
|
|
|
11
|
-
import { CarService, RulesService, LoaderService, ConfigService } from '../services';
|
|
11
|
+
import { CarService, RulesService, LoaderService, ConfigService, ImagesService } from '../services';
|
|
12
12
|
import { MobileTransformer } from '../transformers/mobile.transformer';
|
|
13
|
-
import { convertToCSV } from '../utils/converter.utils';
|
|
14
13
|
import { Exporter } from '../interfaces/exporter.interface';
|
|
14
|
+
import { convertToCSV } from '../utils/converter.utils';
|
|
15
15
|
import { FileType } from '../enums';
|
|
16
16
|
|
|
17
17
|
@Injectable()
|
|
@@ -21,6 +21,7 @@ export class MobileExporter implements Exporter {
|
|
|
21
21
|
private configService: ConfigService,
|
|
22
22
|
private rulesService: RulesService,
|
|
23
23
|
private loaderService: LoaderService,
|
|
24
|
+
private imageService: ImagesService,
|
|
24
25
|
private moduleRef: ModuleRef,
|
|
25
26
|
) { }
|
|
26
27
|
|
|
@@ -30,6 +31,7 @@ export class MobileExporter implements Exporter {
|
|
|
30
31
|
await CronicleUtils.emptyDir(this.configService.config.pathesConfig.localConvertedDirectory);
|
|
31
32
|
|
|
32
33
|
const mobileTransformer = await this.moduleRef.create(MobileTransformer);
|
|
34
|
+
const imagesPath = [];
|
|
33
35
|
|
|
34
36
|
await this.loaderService.process(async (chunk: FsChunkInterface) => {
|
|
35
37
|
chunk.data = chunk.data.filter(e => this.carService.filterCar(e, this.rulesService.rules));
|
|
@@ -38,9 +40,12 @@ export class MobileExporter implements Exporter {
|
|
|
38
40
|
for (let i = 0; i < chunk.data.length; i += 1) {
|
|
39
41
|
const data = chunk.data[i];
|
|
40
42
|
const { car, images } = data;
|
|
41
|
-
// console.time(`Transform ${data.car._id}`);
|
|
42
43
|
const transformedData = await mobileTransformer.transform(car, images);
|
|
43
|
-
|
|
44
|
+
|
|
45
|
+
if (this.configService.config.downloadImage) {
|
|
46
|
+
const urls = await this.imageService.getImages(car, images, this.configService.config.imageType, true);
|
|
47
|
+
imagesPath.push(...await this.imageService.downloadInParallel(car.metaData.vehicleId, urls, this.configService.config));
|
|
48
|
+
}
|
|
44
49
|
|
|
45
50
|
await writeFile(fileName, Buffer.from(`${convertToCSV(transformedData)}\n`), { flag: 'a' });
|
|
46
51
|
|
|
@@ -50,9 +55,9 @@ export class MobileExporter implements Exporter {
|
|
|
50
55
|
});
|
|
51
56
|
|
|
52
57
|
const fileZIP = normalize(`${parse(fileName).dir}/${parse(fileName).name}.${FileType.ZIP}`);
|
|
53
|
-
|
|
58
|
+
|
|
54
59
|
if (await CronicleUtils.exists(fileName)) {
|
|
55
|
-
await Zipper.zip({ fileList: [fileName], dest: fileZIP });
|
|
60
|
+
await Zipper.zip({ fileList: [fileName, ...imagesPath], dest: fileZIP });
|
|
56
61
|
}
|
|
57
62
|
console.log(this.carService.counter.getCountedCars());
|
|
58
63
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createWriteStream } from 'fs';
|
|
2
|
+
import { extname } from 'path';
|
|
3
|
+
import axios from 'axios';
|
|
2
4
|
|
|
3
5
|
import { Injectable } from '@nestjs/common';
|
|
4
6
|
|
|
@@ -7,9 +9,10 @@ import { ImboImageResponseInterface } from '@bytebrand/chunkable';
|
|
|
7
9
|
import { DataSchemes } from '@bytebrand/car-schema';
|
|
8
10
|
|
|
9
11
|
import { FB_MAX_IMAGE_COUNT_FOR_DEAFULT } from '../constants/fb.constants';
|
|
10
|
-
import { isNotEmptyArray } from '../utils';
|
|
11
12
|
import { ConfigService } from './config.service';
|
|
12
13
|
import { UserService } from './user.service';
|
|
14
|
+
import { isNotEmptyArray } from '../utils';
|
|
15
|
+
import { Config } from '../configs/config';
|
|
13
16
|
import { ImageType } from '../enums';
|
|
14
17
|
|
|
15
18
|
@Injectable()
|
|
@@ -84,30 +87,21 @@ export class ImagesService {
|
|
|
84
87
|
|
|
85
88
|
constructor(private readonly configService: ConfigService, private readonly userService: UserService) { }
|
|
86
89
|
|
|
87
|
-
async getImages(
|
|
88
|
-
car: DataSchemes.Car,
|
|
89
|
-
images: ImboImageResponseInterface[],
|
|
90
|
-
imageType: ImageType,
|
|
91
|
-
ignoreDefImages: boolean,
|
|
92
|
-
): Promise<Array<string>> {
|
|
90
|
+
async getImages(car: DataSchemes.Car, images: ImboImageResponseInterface[], imageType: ImageType, ignoreDefImages: boolean): Promise<Array<string>> {
|
|
93
91
|
if (imageType === ImageType.default) {
|
|
94
|
-
return this.getDefaultImages(car, images, ignoreDefImages);
|
|
92
|
+
return this.getDefaultImages(car._id, images, ignoreDefImages);
|
|
95
93
|
} else {
|
|
96
|
-
return await this.getMarketingImages(car, images, ignoreDefImages);
|
|
94
|
+
return await this.getMarketingImages(car._id, images, ignoreDefImages);
|
|
97
95
|
}
|
|
98
96
|
}
|
|
99
97
|
|
|
100
|
-
getDefaultImages(
|
|
101
|
-
car: DataSchemes.Car,
|
|
102
|
-
images: ImboImageResponseInterface[],
|
|
103
|
-
ignoreDefImages: boolean,
|
|
104
|
-
): Array<string> {
|
|
98
|
+
getDefaultImages(carId: string, images: ImboImageResponseInterface[], ignoreDefImages: boolean): Array<string> {
|
|
105
99
|
if (!isNotEmptyArray(images)) {
|
|
106
100
|
return this.DEFAULT_IMAGES;
|
|
107
101
|
}
|
|
108
102
|
|
|
109
103
|
const sortedImages = this.sortImages(images);
|
|
110
|
-
const transformedImages = this.generateImageLinks(sortedImages,
|
|
104
|
+
const transformedImages = this.generateImageLinks(sortedImages, carId);
|
|
111
105
|
|
|
112
106
|
if (!ignoreDefImages && transformedImages.length < 17) {
|
|
113
107
|
transformedImages.push(...this.DEFAULT_IMAGES);
|
|
@@ -122,11 +116,7 @@ export class ImagesService {
|
|
|
122
116
|
});
|
|
123
117
|
}
|
|
124
118
|
|
|
125
|
-
async getMarketingImages(
|
|
126
|
-
car: DataSchemes.Car,
|
|
127
|
-
images: ImboImageResponseInterface[],
|
|
128
|
-
ignoreDefImages: boolean,
|
|
129
|
-
): Promise<string[]> {
|
|
119
|
+
async getMarketingImages(carId: string, images: ImboImageResponseInterface[], ignoreDefImages: boolean): Promise<string[]> {
|
|
130
120
|
const imageUrl = this.configService.config.marketingImageUrl;
|
|
131
121
|
|
|
132
122
|
const transformedImages = this.transformImagesArrayIntoObject(this.sortImages(images));
|
|
@@ -140,7 +130,7 @@ export class ImagesService {
|
|
|
140
130
|
const imageCode = marketingImage.marketingImageCodes[i];
|
|
141
131
|
const imageId = transformedImages[imageCode];
|
|
142
132
|
if (imageId) {
|
|
143
|
-
imageUrls.push(this.getImagePathWithExtension(imageUrl,
|
|
133
|
+
imageUrls.push(this.getImagePathWithExtension(imageUrl, carId, imageId, config));
|
|
144
134
|
break;
|
|
145
135
|
}
|
|
146
136
|
}
|
|
@@ -167,24 +157,45 @@ export class ImagesService {
|
|
|
167
157
|
return `${imageUrl}/${[carId[0], carId[1], carId[2], carId].join('/')}`;
|
|
168
158
|
}
|
|
169
159
|
|
|
170
|
-
protected getImagePathWithExtension(
|
|
171
|
-
imageUrl: string,
|
|
172
|
-
carId: string,
|
|
173
|
-
imageId: string,
|
|
174
|
-
config: MarketingImageConfigsInterface,
|
|
175
|
-
): string {
|
|
160
|
+
protected getImagePathWithExtension(imageUrl: string, carId: string, imageId: string, config: MarketingImageConfigsInterface): string {
|
|
176
161
|
const { channelCode, imageExtension } = config;
|
|
177
162
|
return `${this.getChannelPath(imageUrl, carId)}/${channelCode.replace(/ /g, '-')}-${imageId}.${imageExtension}`;
|
|
178
163
|
}
|
|
179
164
|
|
|
180
165
|
sortImages(images: ImboImageResponseInterface[]): ImboImageResponseInterface[] {
|
|
181
166
|
return images.sort((a, b) => {
|
|
182
|
-
const imageCodePredictedA = a.imageCodePredicted.includes('-') ? a.imageCodePredicted.split('-')[
|
|
183
|
-
const imageCodePredictedB = b.imageCodePredicted.includes('-') ? b.imageCodePredicted.split('-')[
|
|
167
|
+
const imageCodePredictedA = a.imageCodePredicted.includes('-') ? a.imageCodePredicted.split('-')[0] : a.imageCodePredicted;
|
|
168
|
+
const imageCodePredictedB = b.imageCodePredicted.includes('-') ? b.imageCodePredicted.split('-')[0] : b.imageCodePredicted;
|
|
184
169
|
const orderA = this.IMAGES_SORT_ORDER[imageCodePredictedA] || 100;
|
|
185
170
|
const orderB = this.IMAGES_SORT_ORDER[imageCodePredictedB] || 100;
|
|
186
|
-
|
|
171
|
+
|
|
187
172
|
return orderA - orderB;
|
|
188
173
|
});
|
|
189
174
|
}
|
|
175
|
+
|
|
176
|
+
async downloadInParallel(vehicleId: string, urls: string[], config: Config): Promise<string[]> {
|
|
177
|
+
const downloadPromises = urls.map(
|
|
178
|
+
async (url: string, j: number) => await this.download(
|
|
179
|
+
url,
|
|
180
|
+
`${config.pathesConfig.localConvertedDirectory}/${vehicleId}_${(j + 1).toLocaleString('en-US', { minimumIntegerDigits: 2 })}.jpg`
|
|
181
|
+
)
|
|
182
|
+
);
|
|
183
|
+
return Promise.all(downloadPromises);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private async download(url: string, imagePath: string) {
|
|
187
|
+
return new Promise<string>(async resolve => {
|
|
188
|
+
try {
|
|
189
|
+
const response = await axios.get(url, { responseType: 'stream' });
|
|
190
|
+
response.data.pipe(
|
|
191
|
+
createWriteStream(imagePath)
|
|
192
|
+
.on('close', () => { console.log(`Downloaded: ${url}`, imagePath); resolve(imagePath); })
|
|
193
|
+
.on('error', (err) => { console.error(`Failed to download: ${url}`); resolve(null); })
|
|
194
|
+
);
|
|
195
|
+
} catch (error) {
|
|
196
|
+
console.error(`Failed to download: ${url}`);
|
|
197
|
+
resolve(null);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
};
|
|
190
201
|
}
|
|
@@ -12,6 +12,7 @@ import { MappingRules } from '../interfaces/mapping-rules.interface';
|
|
|
12
12
|
import { isNotEmptyArray } from '../utils';
|
|
13
13
|
import { ConfigService } from '../services';
|
|
14
14
|
import { ExportType } from '../enums';
|
|
15
|
+
import axios from "axios";
|
|
15
16
|
|
|
16
17
|
const vinGenerator = require('vin-generator');
|
|
17
18
|
|
|
@@ -24,7 +25,7 @@ export class MobileTransformer {
|
|
|
24
25
|
private userService: UserService,
|
|
25
26
|
private configService: ConfigService,
|
|
26
27
|
private imagesService: ImagesService,
|
|
27
|
-
) {}
|
|
28
|
+
) { }
|
|
28
29
|
|
|
29
30
|
async transform(carData: DataSchemes.Car, images: ImboImageResponseInterface[]): Promise<unknown> {
|
|
30
31
|
this.currentCar = carData;
|
|
@@ -364,6 +365,9 @@ export class MobileTransformer {
|
|
|
364
365
|
false,
|
|
365
366
|
);
|
|
366
367
|
let imagesAsALine;
|
|
368
|
+
// console.log(' --> transformedImages', transformedImages)
|
|
369
|
+
if (this.configService.config.campaign == ExportType.EVIUM) return null;
|
|
370
|
+
|
|
367
371
|
if (isNotEmptyArray(transformedImages)) {
|
|
368
372
|
imagesAsALine = transformedImages.map((el: string) => el).join(';');
|
|
369
373
|
}
|