@bytebrand/export-cars 1.26.24 → 1.26.26

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.
Files changed (33) hide show
  1. package/README.md +30 -0
  2. package/dist/configs/config.d.ts +3 -1
  3. package/dist/configs/config.js +2 -0
  4. package/dist/configs/config.js.map +1 -1
  5. package/dist/configs/google.config.js +9 -0
  6. package/dist/configs/google.config.js.map +1 -1
  7. package/dist/export.command.js +1 -0
  8. package/dist/export.command.js.map +1 -1
  9. package/dist/exporters/google.exporter.js +9 -1
  10. package/dist/exporters/google.exporter.js.map +1 -1
  11. package/dist/interfaces/params.interface.d.ts +8 -0
  12. package/dist/services/feed-items-bigquery.writer.d.ts +17 -0
  13. package/dist/services/feed-items-bigquery.writer.js +96 -0
  14. package/dist/services/feed-items-bigquery.writer.js.map +1 -0
  15. package/dist/transformers/google-feed-envkv.d.ts +1 -0
  16. package/dist/transformers/google-feed-envkv.js +28 -18
  17. package/dist/transformers/google-feed-envkv.js.map +1 -1
  18. package/dist/transformers/google-feed-item-row.d.ts +25 -0
  19. package/dist/transformers/google-feed-item-row.js +37 -0
  20. package/dist/transformers/google-feed-item-row.js.map +1 -0
  21. package/dist/tsconfig.build.tsbuildinfo +1 -1
  22. package/package.json +3 -2
  23. package/schema/feed-items.json +122 -0
  24. package/src/configs/config.ts +97 -93
  25. package/src/configs/google.config.ts +26 -16
  26. package/src/export.command.ts +38 -37
  27. package/src/exporters/google.exporter.ts +56 -46
  28. package/src/interfaces/params.interface.ts +29 -17
  29. package/src/services/feed-items-bigquery.writer.ts +101 -0
  30. package/src/transformers/google-feed-envkv.ts +40 -20
  31. package/src/transformers/google-feed-item-row.ts +71 -0
  32. package/test/google-feed-envkv.test.ts +13 -0
  33. package/test/google-feed-item-row.test.ts +121 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bytebrand/export-cars",
3
- "version": "1.26.24",
3
+ "version": "1.26.26",
4
4
  "description": "",
5
5
  "author": "bytebrand",
6
6
  "license": "UNLICENSED",
@@ -11,10 +11,11 @@
11
11
  "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
12
12
  "lint": "eslint -c .eslintrc.js --ext .ts src/",
13
13
  "start:dev": "nest start --watch",
14
- "test": "ts-node --transpile-only test/google-feed-envkv.test.ts"
14
+ "test": "ts-node --transpile-only test/google-feed-envkv.test.ts && ts-node --transpile-only test/google-feed-item-row.test.ts"
15
15
  },
16
16
  "dependencies": {
17
17
  "@bytebrand/autode-request": "^1.19.2",
18
+ "@bytebrand/bigquery-logger": "^2.1.2",
18
19
  "@bytebrand/car-basic-data-scope": "^9.11.24",
19
20
  "@bytebrand/car-description": "^1.17.20",
20
21
  "@bytebrand/car-schema": "^3.14.15",
@@ -0,0 +1,122 @@
1
+ [
2
+ {
3
+ "name": "partitionDay",
4
+ "type": "DATE",
5
+ "mode": "NULLABLE",
6
+ "description": "UTC day of exportedAt; the table is partitioned by it"
7
+ },
8
+ {
9
+ "name": "exportedAt",
10
+ "type": "TIMESTAMP",
11
+ "mode": "NULLABLE",
12
+ "description": "Start of the export run (same for every row of a run)"
13
+ },
14
+ {
15
+ "name": "feed",
16
+ "type": "STRING",
17
+ "mode": "NULLABLE",
18
+ "description": "Feed the item was written to ('google')"
19
+ },
20
+ {
21
+ "name": "runId",
22
+ "type": "STRING",
23
+ "mode": "NULLABLE",
24
+ "description": "Cronicle job id of the run"
25
+ },
26
+ {
27
+ "name": "itemId",
28
+ "type": "STRING",
29
+ "mode": "NULLABLE",
30
+ "description": "<g:id> = CBD _id"
31
+ },
32
+ {
33
+ "name": "vehicleId",
34
+ "type": "STRING",
35
+ "mode": "NULLABLE",
36
+ "description": "metaData.vehicleId"
37
+ },
38
+ {
39
+ "name": "make",
40
+ "type": "STRING",
41
+ "mode": "NULLABLE",
42
+ "description": "<g:brand> as sent"
43
+ },
44
+ {
45
+ "name": "model",
46
+ "type": "STRING",
47
+ "mode": "NULLABLE",
48
+ "description": "<g:model> as sent"
49
+ },
50
+ {
51
+ "name": "condition",
52
+ "type": "STRING",
53
+ "mode": "NULLABLE",
54
+ "description": "<g:condition> as sent"
55
+ },
56
+ {
57
+ "name": "fuel",
58
+ "type": "STRING",
59
+ "mode": "NULLABLE",
60
+ "description": "consumption.fuel selector"
61
+ },
62
+ {
63
+ "name": "hybridPlugin",
64
+ "type": "BOOLEAN",
65
+ "mode": "NULLABLE",
66
+ "description": "engineData.hybridPlugin, null when unset"
67
+ },
68
+ {
69
+ "name": "engine",
70
+ "type": "STRING",
71
+ "mode": "NULLABLE",
72
+ "description": "<g:engine> as sent"
73
+ },
74
+ {
75
+ "name": "electricRange",
76
+ "type": "STRING",
77
+ "mode": "NULLABLE",
78
+ "description": "<g:electric_range> as sent, null when omitted"
79
+ },
80
+ {
81
+ "name": "fuelConsumption",
82
+ "type": "STRING",
83
+ "mode": "NULLABLE",
84
+ "description": "<g:fuel_consumption> as sent, null when omitted"
85
+ },
86
+ {
87
+ "name": "fuelConsumptionDischarged",
88
+ "type": "STRING",
89
+ "mode": "NULLABLE",
90
+ "description": "<g:fuel_consumption_discharged_battery> as sent, null when omitted"
91
+ },
92
+ {
93
+ "name": "energyConsumption",
94
+ "type": "STRING",
95
+ "mode": "NULLABLE",
96
+ "description": "<g:energy_consumption> as sent, null when omitted"
97
+ },
98
+ {
99
+ "name": "co2Emissions",
100
+ "type": "STRING",
101
+ "mode": "NULLABLE",
102
+ "description": "<g:co2_emissions> as sent, null when omitted"
103
+ },
104
+ {
105
+ "name": "rangeSourceField",
106
+ "type": "STRING",
107
+ "mode": "NULLABLE",
108
+ "description": "Car field electric_range came from: battery.wltpRange / battery.wltpTotalRange / battery.batteryRangeElectric"
109
+ },
110
+ {
111
+ "name": "price",
112
+ "type": "FLOAT",
113
+ "mode": "NULLABLE",
114
+ "description": "<g:price> amount as sent (EUR)"
115
+ },
116
+ {
117
+ "name": "availability",
118
+ "type": "STRING",
119
+ "mode": "NULLABLE",
120
+ "description": "<g:availability> as sent"
121
+ }
122
+ ]
@@ -1,93 +1,97 @@
1
- import { Environment, ExportType, ImageType } from '../enums';
2
- import { Params } from '../interfaces/params.interface';
3
-
4
- export class Config {
5
- apikey: string;
6
- campaign: string;
7
- userActionsUrl: string;
8
- marketingImageConfigId: number;
9
- upaUrl: string;
10
- imboUrl: string;
11
- imageUrl: string;
12
- baseImageUrl: string;
13
- marketingImageUrl: string;
14
- metaAiaImageUrl: string;
15
- imageType: ImageType;
16
- downloadImage: boolean;
17
- isNewCar: boolean;
18
- isCriteo: boolean;
19
- exportType: ExportType;
20
- domain: string;
21
- input: {
22
- stringIN: string;
23
- numberIN: string;
24
- dateIN: string;
25
- };
26
- // When set, CarService.filterCar exports a car only if price.currentSalesPrice
27
- // is a valid number in (0, maxCurrentSalesPrice] — missing/invalid price means
28
- // the car is not exported. Unset = no price cap for this export type.
29
- maxCurrentSalesPrice?: number;
30
- pathesConfig: {
31
- localDirectory: string;
32
- localConvertedDirectory: string;
33
- baseFileName: string;
34
- };
35
- outputFiles: string[];
36
- cpc: string;
37
- mappingSpreadsheetId: string;
38
- mappingSpreadsheetTitle: string;
39
- dealersSpreadsheetId: string;
40
- dealersSpreadsheetTitle: string;
41
- metaPushPriorityCsvUrl: string;
42
-
43
- protected environmentConfig = {
44
- [Environment.DEVELOP]: {
45
- domain: 'autode-dev.de',
46
- fbMarketingImageId: 8,
47
- criteoMarketingImagesConfigId: 12,
48
- localConvertedDirectory: 'resources/csv_dev',
49
- remoteDirectory: 'import_dev',
50
- imageUrl: 'https://images.autode-dev.de/cache_test',
51
- baseImageUrl: 'https://images.autode-dev.de',
52
- },
53
- [Environment.PRODUCTION]: {
54
- domain: 'auto.de',
55
- fbMarketingImageId: 1,
56
- criteoMarketingImagesConfigId: 3,
57
- localConvertedDirectory: 'resources/csv_prod',
58
- remoteDirectory: 'import',
59
- imageUrl: 'https://images.auto.de/generated_images',
60
- baseImageUrl: 'https://images.auto.de',
61
- },
62
- };
63
-
64
- constructor(params: Params) {
65
- this.apikey = params.apikey;
66
- this.exportType = params.exportType;
67
- this.cpc = params.cpc;
68
- this.domain = this.environmentConfig[params.environment].domain;
69
- this.baseImageUrl = this.environmentConfig[params.environment].baseImageUrl;
70
- this.marketingImageUrl = this.environmentConfig[params.environment].imageUrl;
71
- this.mappingSpreadsheetId = '147qz9sTgs1wi13ua8YKa0IgfxAn1X_qH6LkGnAnzeyY';
72
- this.dealersSpreadsheetId = '1gWrcA-uRTpna1h66Mbe2Y7NA0tIhlUaQXBcS4a_UzOw';
73
- this.dealersSpreadsheetTitle = 'dealers_mapping';
74
- this.metaPushPriorityCsvUrl = params.metaPushPriorityCsvUrl;
75
- this.userActionsUrl = `https://www.${this.domain}/service/user-actions`;
76
- this.imboUrl = `https://www.${this.domain}/service/image-imbo`;
77
- this.imageUrl = `https://images.${this.domain}`;
78
- // Same FTP_ROOT (/generated_images) image-generator's MetaAiaImagesService saves to —
79
- // independent of marketingImageUrl, which points legacy fb/criteo channels at their own path.
80
- this.metaAiaImageUrl = `${this.imageUrl}/generated_images`;
81
- this.upaUrl = `https://www.${this.domain}/service/user-profile-admin`;
82
- this.input = {
83
- stringIN: params.String,
84
- numberIN: params.Number,
85
- dateIN: params.Date,
86
- };
87
- this.pathesConfig = {
88
- localDirectory: 'resources/cars-export',
89
- localConvertedDirectory: 'resources/default-export',
90
- baseFileName: 'cars',
91
- };
92
- }
93
- }
1
+ import { Environment, ExportType, ImageType } from '../enums';
2
+ import { FeedItemsBigQueryTarget, Params } from '../interfaces/params.interface';
3
+
4
+ export class Config {
5
+ apikey: string;
6
+ campaign: string;
7
+ userActionsUrl: string;
8
+ marketingImageConfigId: number;
9
+ upaUrl: string;
10
+ imboUrl: string;
11
+ imageUrl: string;
12
+ baseImageUrl: string;
13
+ marketingImageUrl: string;
14
+ metaAiaImageUrl: string;
15
+ imageType: ImageType;
16
+ downloadImage: boolean;
17
+ isNewCar: boolean;
18
+ isCriteo: boolean;
19
+ exportType: ExportType;
20
+ domain: string;
21
+ input: {
22
+ stringIN: string;
23
+ numberIN: string;
24
+ dateIN: string;
25
+ };
26
+ // When set, CarService.filterCar exports a car only if price.currentSalesPrice
27
+ // is a valid number in (0, maxCurrentSalesPrice] — missing/invalid price means
28
+ // the car is not exported. Unset = no price cap for this export type.
29
+ maxCurrentSalesPrice?: number;
30
+ pathesConfig: {
31
+ localDirectory: string;
32
+ localConvertedDirectory: string;
33
+ baseFileName: string;
34
+ };
35
+ outputFiles: string[];
36
+ cpc: string;
37
+ mappingSpreadsheetId: string;
38
+ mappingSpreadsheetTitle: string;
39
+ dealersSpreadsheetId: string;
40
+ dealersSpreadsheetTitle: string;
41
+ metaPushPriorityCsvUrl: string;
42
+ // Cronicle job id of the run (set by ExportRunner), stored with the feed item rows.
43
+ runId?: string;
44
+ // Set only when the job configures the feed items BigQuery table; unset = no BigQuery calls.
45
+ feedItemsBigQuery?: FeedItemsBigQueryTarget;
46
+
47
+ protected environmentConfig = {
48
+ [Environment.DEVELOP]: {
49
+ domain: 'autode-dev.de',
50
+ fbMarketingImageId: 8,
51
+ criteoMarketingImagesConfigId: 12,
52
+ localConvertedDirectory: 'resources/csv_dev',
53
+ remoteDirectory: 'import_dev',
54
+ imageUrl: 'https://images.autode-dev.de/cache_test',
55
+ baseImageUrl: 'https://images.autode-dev.de',
56
+ },
57
+ [Environment.PRODUCTION]: {
58
+ domain: 'auto.de',
59
+ fbMarketingImageId: 1,
60
+ criteoMarketingImagesConfigId: 3,
61
+ localConvertedDirectory: 'resources/csv_prod',
62
+ remoteDirectory: 'import',
63
+ imageUrl: 'https://images.auto.de/generated_images',
64
+ baseImageUrl: 'https://images.auto.de',
65
+ },
66
+ };
67
+
68
+ constructor(params: Params) {
69
+ this.apikey = params.apikey;
70
+ this.exportType = params.exportType;
71
+ this.cpc = params.cpc;
72
+ this.domain = this.environmentConfig[params.environment].domain;
73
+ this.baseImageUrl = this.environmentConfig[params.environment].baseImageUrl;
74
+ this.marketingImageUrl = this.environmentConfig[params.environment].imageUrl;
75
+ this.mappingSpreadsheetId = '147qz9sTgs1wi13ua8YKa0IgfxAn1X_qH6LkGnAnzeyY';
76
+ this.dealersSpreadsheetId = '1gWrcA-uRTpna1h66Mbe2Y7NA0tIhlUaQXBcS4a_UzOw';
77
+ this.dealersSpreadsheetTitle = 'dealers_mapping';
78
+ this.metaPushPriorityCsvUrl = params.metaPushPriorityCsvUrl;
79
+ this.userActionsUrl = `https://www.${this.domain}/service/user-actions`;
80
+ this.imboUrl = `https://www.${this.domain}/service/image-imbo`;
81
+ this.imageUrl = `https://images.${this.domain}`;
82
+ // Same FTP_ROOT (/generated_images) image-generator's MetaAiaImagesService saves to —
83
+ // independent of marketingImageUrl, which points legacy fb/criteo channels at their own path.
84
+ this.metaAiaImageUrl = `${this.imageUrl}/generated_images`;
85
+ this.upaUrl = `https://www.${this.domain}/service/user-profile-admin`;
86
+ this.input = {
87
+ stringIN: params.String,
88
+ numberIN: params.Number,
89
+ dateIN: params.Date,
90
+ };
91
+ this.pathesConfig = {
92
+ localDirectory: 'resources/cars-export',
93
+ localConvertedDirectory: 'resources/default-export',
94
+ baseFileName: 'cars',
95
+ };
96
+ }
97
+ }
@@ -1,16 +1,26 @@
1
- import { ExportType, FileType, ImageType } from 'src/enums';
2
- import { resolveMaxFeedSalesPrice } from '../constants/feed-price.constants';
3
- import { Params } from '../interfaces/params.interface';
4
- import { Config } from './config';
5
-
6
- export class GoogleConfig extends Config {
7
- constructor(params: Params) {
8
- super(params);
9
- this.pathesConfig.localConvertedDirectory = `resources/${ExportType.GOOGLE}-export`;
10
- this.imageType = ImageType.default;
11
- this.maxCurrentSalesPrice = resolveMaxFeedSalesPrice(params.maxCurrentSalesPrice);
12
- this.outputFiles = [
13
- `${this.pathesConfig.localConvertedDirectory}/api_export_cars_${ExportType.GOOGLE}.${FileType.XML}`,
14
- ];
15
- }
16
- }
1
+ import { ExportType, FileType, ImageType } from 'src/enums';
2
+ import { resolveMaxFeedSalesPrice } from '../constants/feed-price.constants';
3
+ import { Params } from '../interfaces/params.interface';
4
+ import { Config } from './config';
5
+
6
+ const trimmed = (value: unknown): string => (typeof value === 'string' ? value.trim() : '');
7
+
8
+ export class GoogleConfig extends Config {
9
+ constructor(params: Params) {
10
+ super(params);
11
+ this.pathesConfig.localConvertedDirectory = `resources/${ExportType.GOOGLE}-export`;
12
+ this.imageType = ImageType.default;
13
+ this.maxCurrentSalesPrice = resolveMaxFeedSalesPrice(params.maxCurrentSalesPrice);
14
+ const feedItemsBigQuery = {
15
+ project: trimmed(params.feedItemsBqProject),
16
+ dataset: trimmed(params.feedItemsBqDataset),
17
+ table: trimmed(params.feedItemsBqTable),
18
+ };
19
+ if (feedItemsBigQuery.project && feedItemsBigQuery.dataset && feedItemsBigQuery.table) {
20
+ this.feedItemsBigQuery = feedItemsBigQuery;
21
+ }
22
+ this.outputFiles = [
23
+ `${this.pathesConfig.localConvertedDirectory}/api_export_cars_${ExportType.GOOGLE}.${FileType.XML}`,
24
+ ];
25
+ }
26
+ }
@@ -1,37 +1,38 @@
1
- import { createInterface } from 'readline';
2
- import { Command, CommandRunner } from 'nest-commander';
3
- import { ConfigService } from './services';
4
- import { Input } from './interfaces/input.interface';
5
- import { exportSchema } from './export.schema';
6
- import { ModuleRef } from '@nestjs/core';
7
- import { Exporter } from './interfaces/exporter.interface';
8
- import { LoggerService } from '@bytebrand/cronicle-helper';
9
-
10
- @Command({
11
- name: 'export',
12
- options: { isDefault: true },
13
- })
14
- export class ExportRunner extends CommandRunner {
15
- constructor(
16
- private configService: ConfigService,
17
- private moduleRef: ModuleRef,
18
- ) {
19
- super();
20
- }
21
-
22
- async run(): Promise<void> {
23
- const rl = createInterface({ input: process.stdin });
24
- await new Promise<void>((resolve, reject) => {
25
- rl.on('line', async (data: string) => {
26
- const input = JSON.parse(data) as Input;
27
- LoggerService.log(`Running job: ${input.id} - ${JSON.stringify(input)} \n`);
28
-
29
- this.configService.config = new exportSchema[input.params.exportType].config(input.params);
30
- const exporter = await this.moduleRef.create<Exporter>(exportSchema[input.params.exportType].exporter);
31
- await exporter.export().catch(reject);
32
-
33
- resolve(rl.close());
34
- });
35
- });
36
- }
37
- }
1
+ import { createInterface } from 'readline';
2
+ import { Command, CommandRunner } from 'nest-commander';
3
+ import { ConfigService } from './services';
4
+ import { Input } from './interfaces/input.interface';
5
+ import { exportSchema } from './export.schema';
6
+ import { ModuleRef } from '@nestjs/core';
7
+ import { Exporter } from './interfaces/exporter.interface';
8
+ import { LoggerService } from '@bytebrand/cronicle-helper';
9
+
10
+ @Command({
11
+ name: 'export',
12
+ options: { isDefault: true },
13
+ })
14
+ export class ExportRunner extends CommandRunner {
15
+ constructor(
16
+ private configService: ConfigService,
17
+ private moduleRef: ModuleRef,
18
+ ) {
19
+ super();
20
+ }
21
+
22
+ async run(): Promise<void> {
23
+ const rl = createInterface({ input: process.stdin });
24
+ await new Promise<void>((resolve, reject) => {
25
+ rl.on('line', async (data: string) => {
26
+ const input = JSON.parse(data) as Input;
27
+ LoggerService.log(`Running job: ${input.id} - ${JSON.stringify(input)} \n`);
28
+
29
+ this.configService.config = new exportSchema[input.params.exportType].config(input.params);
30
+ this.configService.config.runId = input.id;
31
+ const exporter = await this.moduleRef.create<Exporter>(exportSchema[input.params.exportType].exporter);
32
+ await exporter.export().catch(reject);
33
+
34
+ resolve(rl.close());
35
+ });
36
+ });
37
+ }
38
+ }
@@ -1,46 +1,56 @@
1
- import { Injectable } from '@nestjs/common';
2
- import { ModuleRef } from '@nestjs/core';
3
-
4
- import { CronicleUtils } from '@bytebrand/cronicle-helper';
5
- import { FsChunkInterface } from '@bytebrand/chunkable';
6
-
7
- import { CarService, ConfigService, LoaderService, RulesService } from '../services';
8
- import { GoogleTransformer } from '../transformers/google.transformer';
9
- import { GoogleXmlBuilder } from '../formats/google-xml.builder';
10
- import { Exporter } from '../interfaces/exporter.interface';
11
-
12
- @Injectable()
13
- export class GoogleExporter implements Exporter {
14
- constructor(
15
- private carService: CarService,
16
- private configService: ConfigService,
17
- private rulesService: RulesService,
18
- private loaderService: LoaderService,
19
- private moduleRef: ModuleRef,
20
- ) { }
21
-
22
- async export(): Promise<void> {
23
- const [fileName] = this.configService.config.outputFiles;
24
- await CronicleUtils.ensureDir(this.configService.config.pathesConfig.localConvertedDirectory);
25
- await CronicleUtils.emptyDir(this.configService.config.pathesConfig.localConvertedDirectory);
26
-
27
- const googleXmlBuilder = new GoogleXmlBuilder(fileName);
28
- await googleXmlBuilder.buildStart(this.configService.config.domain);
29
-
30
- const googleTransformer = await this.moduleRef.create(GoogleTransformer);
31
-
32
- await this.loaderService.process(async (chunk: FsChunkInterface) => {
33
- chunk.data = chunk.data.filter(car => this.carService.filterCar(car, this.rulesService.rules));
34
-
35
- for (const data of chunk.data) {
36
- const car = await googleTransformer.transform(data);
37
- if (car)
38
- await googleXmlBuilder.addItem(car);
39
- this.carService.counter.countCar(data.images);
40
- }
41
- });
42
-
43
- await googleXmlBuilder.buildEnd();
44
- console.log(this.carService.counter.getCountedCars());
45
- }
46
- }
1
+ import { Injectable } from '@nestjs/common';
2
+ import { ModuleRef } from '@nestjs/core';
3
+
4
+ import { CronicleUtils } from '@bytebrand/cronicle-helper';
5
+ import { FsChunkInterface } from '@bytebrand/chunkable';
6
+
7
+ import { CarService, ConfigService, LoaderService, RulesService } from '../services';
8
+ import { GoogleTransformer } from '../transformers/google.transformer';
9
+ import { buildGoogleFeedItemRow } from '../transformers/google-feed-item-row';
10
+ import { FeedItemsBigQueryWriter } from '../services/feed-items-bigquery.writer';
11
+ import { GoogleXmlBuilder } from '../formats/google-xml.builder';
12
+ import { Exporter } from '../interfaces/exporter.interface';
13
+
14
+ @Injectable()
15
+ export class GoogleExporter implements Exporter {
16
+ constructor(
17
+ private carService: CarService,
18
+ private configService: ConfigService,
19
+ private rulesService: RulesService,
20
+ private loaderService: LoaderService,
21
+ private moduleRef: ModuleRef,
22
+ ) { }
23
+
24
+ async export(): Promise<void> {
25
+ const [fileName] = this.configService.config.outputFiles;
26
+ await CronicleUtils.ensureDir(this.configService.config.pathesConfig.localConvertedDirectory);
27
+ await CronicleUtils.emptyDir(this.configService.config.pathesConfig.localConvertedDirectory);
28
+
29
+ const googleXmlBuilder = new GoogleXmlBuilder(fileName);
30
+ await googleXmlBuilder.buildStart(this.configService.config.domain);
31
+
32
+ const googleTransformer = await this.moduleRef.create(GoogleTransformer);
33
+
34
+ // Opt-in audit of the sent items (feedItemsBq* job params); null = no BigQuery calls at all.
35
+ const { feedItemsBigQuery, runId } = this.configService.config;
36
+ const feedItemsWriter = feedItemsBigQuery ? new FeedItemsBigQueryWriter(feedItemsBigQuery) : null;
37
+ const exportedAt = new Date();
38
+
39
+ await this.loaderService.process(async (chunk: FsChunkInterface) => {
40
+ chunk.data = chunk.data.filter(car => this.carService.filterCar(car, this.rulesService.rules));
41
+
42
+ for (const data of chunk.data) {
43
+ const car = await googleTransformer.transform(data);
44
+ if (car) {
45
+ await googleXmlBuilder.addItem(car);
46
+ feedItemsWriter?.add(buildGoogleFeedItemRow(data.car, car, runId, exportedAt));
47
+ }
48
+ this.carService.counter.countCar(data.images);
49
+ }
50
+ });
51
+
52
+ await googleXmlBuilder.buildEnd();
53
+ await feedItemsWriter?.close();
54
+ console.log(this.carService.counter.getCountedCars());
55
+ }
56
+ }
@@ -1,17 +1,29 @@
1
- import { Environment, ExportType } from '../enums';
2
-
3
- export interface Params {
4
- environment: Environment;
5
- exportType?: ExportType;
6
- apikey?: string;
7
- String?: string;
8
- Number?: string;
9
- Date?: string;
10
- isNewCar?: boolean;
11
- cpc?: string;
12
- ownerId?: string;
13
- metaPushPriorityCsvUrl?: string;
14
- // Cronicle plugin param (text field, so it arrives as a string); overrides the
15
- // default MAX_FEED_SALES_PRICE_EUR cap in the configs that apply one.
16
- maxCurrentSalesPrice?: string;
17
- }
1
+ import { Environment, ExportType } from '../enums';
2
+
3
+ export interface Params {
4
+ environment: Environment;
5
+ exportType?: ExportType;
6
+ apikey?: string;
7
+ String?: string;
8
+ Number?: string;
9
+ Date?: string;
10
+ isNewCar?: boolean;
11
+ cpc?: string;
12
+ ownerId?: string;
13
+ metaPushPriorityCsvUrl?: string;
14
+ // Cronicle plugin param (text field, so it arrives as a string); overrides the
15
+ // default MAX_FEED_SALES_PRICE_EUR cap in the configs that apply one.
16
+ maxCurrentSalesPrice?: string;
17
+ // Google feed only, opt-in: when all three are set, every exported item is also written to
18
+ // `<project>.<dataset>.<table>` in BigQuery (schema/feed-items.json, see README).
19
+ feedItemsBqProject?: string;
20
+ feedItemsBqDataset?: string;
21
+ feedItemsBqTable?: string;
22
+ }
23
+
24
+ // Parsed from feedItemsBq* by GoogleConfig.
25
+ export interface FeedItemsBigQueryTarget {
26
+ project: string;
27
+ dataset: string;
28
+ table: string;
29
+ }