@unchainedshop/core 4.6.0 → 4.6.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/lib/bulk-exporter/createBulkExporter.d.ts +33 -0
- package/lib/bulk-exporter/createBulkExporter.js +46 -0
- package/lib/bulk-exporter/handlers/exportAssortmentsHandler.d.ts +38 -0
- package/lib/bulk-exporter/handlers/exportAssortmentsHandler.js +137 -0
- package/lib/bulk-exporter/handlers/exportFiltersHandler.d.ts +26 -0
- package/lib/bulk-exporter/handlers/exportFiltersHandler.js +88 -0
- package/lib/bulk-exporter/handlers/exportProductsHandler.d.ts +40 -0
- package/lib/bulk-exporter/handlers/exportProductsHandler.js +238 -0
- package/lib/bulk-exporter/handlers/exportUsersHandler.d.ts +40 -0
- package/lib/bulk-exporter/handlers/exportUsersHandler.js +340 -0
- package/lib/bulk-exporter/handlers/generateCSVFileAndUrl.d.ts +13 -0
- package/lib/bulk-exporter/handlers/generateCSVFileAndUrl.js +21 -0
- package/lib/bulk-exporter/handlers/toCSV.d.ts +2 -0
- package/lib/bulk-exporter/handlers/toCSV.js +5 -0
- package/lib/bulk-exporter/index.d.ts +3 -0
- package/lib/bulk-exporter/index.js +3 -0
- package/lib/core-index.d.ts +7 -1
- package/lib/core-index.js +5 -1
- package/lib/directors/WorkerAdapter.d.ts +2 -0
- package/package.json +1 -1
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import exportProductsHandler from './handlers/exportProductsHandler.ts';
|
|
2
|
+
import exportAssortmentsHandler from './handlers/exportAssortmentsHandler.ts';
|
|
3
|
+
import exportFiltersHandler from './handlers/exportFiltersHandler.ts';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import type { CSVFileResult } from './handlers/generateCSVFileAndUrl.ts';
|
|
6
|
+
export declare const EXPORTS_DIRECTORY = "exports";
|
|
7
|
+
export type ExportFiles = Record<string, CSVFileResult | null>;
|
|
8
|
+
export interface BulkExportOperationResult {
|
|
9
|
+
entity: string;
|
|
10
|
+
success: boolean;
|
|
11
|
+
files: ExportFiles;
|
|
12
|
+
}
|
|
13
|
+
export interface BulkExportHandler<T = unknown> {
|
|
14
|
+
payloadSchema?: z.ZodObject<z.ZodRawShape>;
|
|
15
|
+
(params: Record<string, unknown>, locales: string[], unchainedAPI: T): Promise<ExportFiles>;
|
|
16
|
+
}
|
|
17
|
+
export interface BulkExporterOptions {
|
|
18
|
+
handlers?: Record<string, BulkExportHandler>;
|
|
19
|
+
}
|
|
20
|
+
export default function createBulkExporterFactory(bulkExporterOptions?: BulkExporterOptions): {
|
|
21
|
+
createBulkExporter: ({ entity }: {
|
|
22
|
+
entity: string;
|
|
23
|
+
}) => {
|
|
24
|
+
validate: (payload: Record<string, unknown>) => Promise<void>;
|
|
25
|
+
execute: <T>(payload: Record<string, unknown>, locales: string[], unchainedApi: T) => Promise<({
|
|
26
|
+
entity: string;
|
|
27
|
+
success: boolean;
|
|
28
|
+
files: ExportFiles;
|
|
29
|
+
} | null)[]>;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
export type BulkExporter = ReturnType<typeof createBulkExporterFactory>;
|
|
33
|
+
export { exportAssortmentsHandler, exportProductsHandler, exportFiltersHandler };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import exportProductsHandler from "./handlers/exportProductsHandler.js";
|
|
2
|
+
import exportAssortmentsHandler from "./handlers/exportAssortmentsHandler.js";
|
|
3
|
+
import exportFiltersHandler from "./handlers/exportFiltersHandler.js";
|
|
4
|
+
import { createLogger } from '@unchainedshop/logger';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import exportUsersHandler from "./handlers/exportUsersHandler.js";
|
|
7
|
+
const logger = createLogger('unchained:bulk-export');
|
|
8
|
+
export const EXPORTS_DIRECTORY = 'exports';
|
|
9
|
+
let bulkOperationHandlers = {};
|
|
10
|
+
export default function createBulkExporterFactory(bulkExporterOptions) {
|
|
11
|
+
bulkOperationHandlers = {
|
|
12
|
+
ASSORTMENTS: exportAssortmentsHandler,
|
|
13
|
+
PRODUCTS: exportProductsHandler,
|
|
14
|
+
FILTERS: exportFiltersHandler,
|
|
15
|
+
USER: exportUsersHandler,
|
|
16
|
+
...(bulkExporterOptions?.handlers || {}),
|
|
17
|
+
};
|
|
18
|
+
const createBulkExporter = ({ entity }) => {
|
|
19
|
+
const type = entity.toUpperCase();
|
|
20
|
+
const exportHandler = bulkOperationHandlers[type];
|
|
21
|
+
if (!exportHandler) {
|
|
22
|
+
throw new Error(`Export entity (${entity}) is not supported`);
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
validate: async (payload) => {
|
|
26
|
+
logger.debug(`Validating ${type} export payload`);
|
|
27
|
+
try {
|
|
28
|
+
if (exportHandler.payloadSchema) {
|
|
29
|
+
exportHandler.payloadSchema.parse(payload);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
throw new Error(`${type}: ${e.message}`);
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
execute: async (payload, locales, unchainedApi) => {
|
|
37
|
+
const files = await exportHandler(payload, locales, unchainedApi);
|
|
38
|
+
return [{ entity: type, success: true, files }, null];
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
return {
|
|
43
|
+
createBulkExporter,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export { exportAssortmentsHandler, exportProductsHandler, exportFiltersHandler };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { UnchainedCore } from '../../core-index.ts';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
export declare const AssortmentExportPayloadSchema: z.ZodObject<{
|
|
4
|
+
exportAssortments: z.ZodOptional<z.ZodBoolean>;
|
|
5
|
+
exportLinks: z.ZodOptional<z.ZodBoolean>;
|
|
6
|
+
exportProducts: z.ZodOptional<z.ZodBoolean>;
|
|
7
|
+
exportFilters: z.ZodOptional<z.ZodBoolean>;
|
|
8
|
+
queryString: z.ZodOptional<z.ZodString>;
|
|
9
|
+
includeInactive: z.ZodOptional<z.ZodBoolean>;
|
|
10
|
+
includeLeaves: z.ZodOptional<z.ZodBoolean>;
|
|
11
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
12
|
+
}, z.core.$strip>;
|
|
13
|
+
export interface AssortmentExportParams {
|
|
14
|
+
exportAssortments?: boolean;
|
|
15
|
+
exportLinks?: boolean;
|
|
16
|
+
exportProducts?: boolean;
|
|
17
|
+
exportFilters?: boolean;
|
|
18
|
+
[key: string]: any;
|
|
19
|
+
}
|
|
20
|
+
declare const exportAssortmentsHandler: {
|
|
21
|
+
({ exportAssortments, exportFilters, exportLinks, exportProducts, ...params }: AssortmentExportParams, locales: string[], unchainedAPI: UnchainedCore): Promise<{
|
|
22
|
+
assortments: import("./generateCSVFileAndUrl.ts").CSVFileResult | null;
|
|
23
|
+
filters: import("./generateCSVFileAndUrl.ts").CSVFileResult | null;
|
|
24
|
+
products: import("./generateCSVFileAndUrl.ts").CSVFileResult | null;
|
|
25
|
+
children: import("./generateCSVFileAndUrl.ts").CSVFileResult | null;
|
|
26
|
+
}>;
|
|
27
|
+
payloadSchema: z.ZodObject<{
|
|
28
|
+
exportAssortments: z.ZodOptional<z.ZodBoolean>;
|
|
29
|
+
exportLinks: z.ZodOptional<z.ZodBoolean>;
|
|
30
|
+
exportProducts: z.ZodOptional<z.ZodBoolean>;
|
|
31
|
+
exportFilters: z.ZodOptional<z.ZodBoolean>;
|
|
32
|
+
queryString: z.ZodOptional<z.ZodString>;
|
|
33
|
+
includeInactive: z.ZodOptional<z.ZodBoolean>;
|
|
34
|
+
includeLeaves: z.ZodOptional<z.ZodBoolean>;
|
|
35
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
36
|
+
}, z.core.$strip>;
|
|
37
|
+
};
|
|
38
|
+
export default exportAssortmentsHandler;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import generateCSVFileAndURL from "./generateCSVFileAndUrl.js";
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { EXPORTS_DIRECTORY } from "../createBulkExporter.js";
|
|
4
|
+
export const AssortmentExportPayloadSchema = z.object({
|
|
5
|
+
exportAssortments: z.boolean().optional(),
|
|
6
|
+
exportLinks: z.boolean().optional(),
|
|
7
|
+
exportProducts: z.boolean().optional(),
|
|
8
|
+
exportFilters: z.boolean().optional(),
|
|
9
|
+
queryString: z.string().optional(),
|
|
10
|
+
includeInactive: z.boolean().optional(),
|
|
11
|
+
includeLeaves: z.boolean().optional(),
|
|
12
|
+
tags: z.array(z.string()).optional(),
|
|
13
|
+
});
|
|
14
|
+
const ASSORTMENT_CSV_SCHEMA = {
|
|
15
|
+
base: ['_id', 'isActive', 'isBase', 'isRoot', 'sequence', 'tags'],
|
|
16
|
+
textFields: ['title', 'subtitle', 'description', 'slug'],
|
|
17
|
+
filterFields: ['_id', 'assortmentId', 'filterId', 'tags', 'sortKey'],
|
|
18
|
+
productFields: ['_id', 'assortmentId', 'productId', 'tags', 'sortKey'],
|
|
19
|
+
childrenFields: ['_id', 'assortmentId', 'childAssortmentId', 'tags', 'sortKey'],
|
|
20
|
+
};
|
|
21
|
+
const buildAssortmentHeaders = (locales) => [
|
|
22
|
+
...ASSORTMENT_CSV_SCHEMA.base,
|
|
23
|
+
...locales.flatMap((locale) => ASSORTMENT_CSV_SCHEMA.textFields.map((field) => `texts.${locale}.${field}`)),
|
|
24
|
+
'meta',
|
|
25
|
+
];
|
|
26
|
+
const buildFilterHeaders = () => ASSORTMENT_CSV_SCHEMA.filterFields;
|
|
27
|
+
const buildProductHeaders = () => ASSORTMENT_CSV_SCHEMA.productFields;
|
|
28
|
+
const buildChildrenHeaders = () => ASSORTMENT_CSV_SCHEMA.childrenFields;
|
|
29
|
+
const fetchAssortmentTexts = async (modules, assortmentId) => {
|
|
30
|
+
const texts = await modules.assortments.texts.findTexts({ assortmentId });
|
|
31
|
+
return texts.reduce((acc, t) => ({ ...acc, [t.locale]: t }), {});
|
|
32
|
+
};
|
|
33
|
+
const exportAssortmentsHandler = async ({ exportAssortments, exportFilters, exportLinks, exportProducts, ...params }, locales, unchainedAPI) => {
|
|
34
|
+
const { modules } = unchainedAPI;
|
|
35
|
+
const assortments = await modules.assortments.findAssortments({ ...params });
|
|
36
|
+
const assortmentRows = [];
|
|
37
|
+
const filterRows = [];
|
|
38
|
+
const productRows = [];
|
|
39
|
+
const childrenRows = [];
|
|
40
|
+
for await (const assortment of assortments) {
|
|
41
|
+
if (exportAssortments) {
|
|
42
|
+
const texts = await fetchAssortmentTexts(modules, assortment._id);
|
|
43
|
+
const row = { ...assortment };
|
|
44
|
+
locales.forEach((locale) => {
|
|
45
|
+
const t = texts[locale] || {};
|
|
46
|
+
ASSORTMENT_CSV_SCHEMA.textFields.forEach((field) => {
|
|
47
|
+
let value = t[field];
|
|
48
|
+
if (Array.isArray(value))
|
|
49
|
+
value = value.join(';');
|
|
50
|
+
row[`texts.${locale}.${field}`] = value ?? '';
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
row['meta'] = typeof assortment?.meta === 'object' ? JSON.stringify(assortment.meta) : '';
|
|
54
|
+
assortmentRows.push(row);
|
|
55
|
+
}
|
|
56
|
+
if (exportFilters) {
|
|
57
|
+
const assortmentFilters = await modules.assortments.filters.findFilters({
|
|
58
|
+
assortmentId: assortment._id,
|
|
59
|
+
});
|
|
60
|
+
filterRows.push(...assortmentFilters.map(({ _id, filterId, tags, sortKey }) => ({
|
|
61
|
+
_id,
|
|
62
|
+
assortmentId: assortment._id,
|
|
63
|
+
filterId,
|
|
64
|
+
tags: tags || '',
|
|
65
|
+
sortKey,
|
|
66
|
+
})));
|
|
67
|
+
}
|
|
68
|
+
if (exportProducts) {
|
|
69
|
+
const assortmentProducts = await modules.assortments.products.findAssortmentProducts({
|
|
70
|
+
assortmentId: assortment._id,
|
|
71
|
+
});
|
|
72
|
+
productRows.push(...assortmentProducts.map(({ _id, productId, tags, sortKey }) => ({
|
|
73
|
+
_id,
|
|
74
|
+
assortmentId: assortment._id,
|
|
75
|
+
productId,
|
|
76
|
+
tags: tags || '',
|
|
77
|
+
sortKey,
|
|
78
|
+
})));
|
|
79
|
+
}
|
|
80
|
+
if (exportLinks) {
|
|
81
|
+
const links = await modules.assortments.links.findLinks({ assortmentId: assortment._id });
|
|
82
|
+
childrenRows.push(...links
|
|
83
|
+
.filter((l) => l.childAssortmentId !== assortment._id)
|
|
84
|
+
.map(({ _id, childAssortmentId, tags, sortKey }) => ({
|
|
85
|
+
_id,
|
|
86
|
+
assortmentId: assortment._id,
|
|
87
|
+
childAssortmentId,
|
|
88
|
+
tags: tags || '',
|
|
89
|
+
sortKey,
|
|
90
|
+
})));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const assortmentsCSV = exportAssortments
|
|
94
|
+
? await generateCSVFileAndURL({
|
|
95
|
+
headers: buildAssortmentHeaders(locales),
|
|
96
|
+
rows: assortmentRows,
|
|
97
|
+
directoryName: EXPORTS_DIRECTORY,
|
|
98
|
+
fileName: 'assortments_export.csv',
|
|
99
|
+
unchainedAPI,
|
|
100
|
+
})
|
|
101
|
+
: null;
|
|
102
|
+
const filtersCSV = exportFilters
|
|
103
|
+
? await generateCSVFileAndURL({
|
|
104
|
+
headers: buildFilterHeaders(),
|
|
105
|
+
rows: filterRows,
|
|
106
|
+
directoryName: EXPORTS_DIRECTORY,
|
|
107
|
+
fileName: 'assortment_filters_export.csv',
|
|
108
|
+
unchainedAPI,
|
|
109
|
+
})
|
|
110
|
+
: null;
|
|
111
|
+
const productsCSV = exportProducts
|
|
112
|
+
? await generateCSVFileAndURL({
|
|
113
|
+
headers: buildProductHeaders(),
|
|
114
|
+
rows: productRows,
|
|
115
|
+
directoryName: EXPORTS_DIRECTORY,
|
|
116
|
+
fileName: 'assortment_products_export.csv',
|
|
117
|
+
unchainedAPI,
|
|
118
|
+
})
|
|
119
|
+
: null;
|
|
120
|
+
const childrenCSV = exportLinks
|
|
121
|
+
? await generateCSVFileAndURL({
|
|
122
|
+
headers: buildChildrenHeaders(),
|
|
123
|
+
rows: childrenRows,
|
|
124
|
+
directoryName: EXPORTS_DIRECTORY,
|
|
125
|
+
fileName: 'assortment_links_export.csv',
|
|
126
|
+
unchainedAPI,
|
|
127
|
+
})
|
|
128
|
+
: null;
|
|
129
|
+
return {
|
|
130
|
+
assortments: assortmentsCSV,
|
|
131
|
+
filters: filtersCSV,
|
|
132
|
+
products: productsCSV,
|
|
133
|
+
children: childrenCSV,
|
|
134
|
+
};
|
|
135
|
+
};
|
|
136
|
+
export default exportAssortmentsHandler;
|
|
137
|
+
exportAssortmentsHandler.payloadSchema = AssortmentExportPayloadSchema;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { UnchainedCore } from '../../core-index.ts';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
export declare const FilterExportPayloadSchema: z.ZodObject<{
|
|
4
|
+
exportFilters: z.ZodOptional<z.ZodBoolean>;
|
|
5
|
+
exportFilterOptions: z.ZodOptional<z.ZodBoolean>;
|
|
6
|
+
queryString: z.ZodOptional<z.ZodString>;
|
|
7
|
+
includeInactive: z.ZodOptional<z.ZodBoolean>;
|
|
8
|
+
}, z.core.$strip>;
|
|
9
|
+
export interface FilterExportParams {
|
|
10
|
+
exportFilters?: boolean;
|
|
11
|
+
exportFilterOptions?: boolean;
|
|
12
|
+
[key: string]: any;
|
|
13
|
+
}
|
|
14
|
+
declare const exportFiltersHandler: {
|
|
15
|
+
({ exportFilterOptions, exportFilters, ...params }: FilterExportParams, locales: string[], unchainedAPI: UnchainedCore): Promise<{
|
|
16
|
+
filters: import("./generateCSVFileAndUrl.ts").CSVFileResult | null;
|
|
17
|
+
filterOptions: import("./generateCSVFileAndUrl.ts").CSVFileResult | null;
|
|
18
|
+
}>;
|
|
19
|
+
payloadSchema: z.ZodObject<{
|
|
20
|
+
exportFilters: z.ZodOptional<z.ZodBoolean>;
|
|
21
|
+
exportFilterOptions: z.ZodOptional<z.ZodBoolean>;
|
|
22
|
+
queryString: z.ZodOptional<z.ZodString>;
|
|
23
|
+
includeInactive: z.ZodOptional<z.ZodBoolean>;
|
|
24
|
+
}, z.core.$strip>;
|
|
25
|
+
};
|
|
26
|
+
export default exportFiltersHandler;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import generateCSVFileAndURL from "./generateCSVFileAndUrl.js";
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { EXPORTS_DIRECTORY } from "../createBulkExporter.js";
|
|
4
|
+
export const FilterExportPayloadSchema = z.object({
|
|
5
|
+
exportFilters: z.boolean().optional(),
|
|
6
|
+
exportFilterOptions: z.boolean().optional(),
|
|
7
|
+
queryString: z.string().optional(),
|
|
8
|
+
includeInactive: z.boolean().optional(),
|
|
9
|
+
});
|
|
10
|
+
const FILTER_CSV_SCHEMA = {
|
|
11
|
+
filterFields: ['_id', 'key', 'type', 'isActive'],
|
|
12
|
+
optionFields: ['optionId', 'filterId', 'value'],
|
|
13
|
+
textFields: ['title', 'subtitle'],
|
|
14
|
+
};
|
|
15
|
+
const buildFilterHeaders = (locales) => [
|
|
16
|
+
...FILTER_CSV_SCHEMA.filterFields,
|
|
17
|
+
...locales.flatMap((locale) => FILTER_CSV_SCHEMA.textFields.map((field) => `texts.${locale}.${field}`)),
|
|
18
|
+
'meta',
|
|
19
|
+
];
|
|
20
|
+
const buildOptionHeaders = (locales) => [
|
|
21
|
+
...FILTER_CSV_SCHEMA.optionFields,
|
|
22
|
+
...locales.flatMap((locale) => FILTER_CSV_SCHEMA.textFields.map((field) => `texts.${locale}.${field}`)),
|
|
23
|
+
];
|
|
24
|
+
const mapTextsToRow = (row, texts, locales) => {
|
|
25
|
+
locales.forEach((locale) => {
|
|
26
|
+
const t = texts[locale] || {};
|
|
27
|
+
FILTER_CSV_SCHEMA.textFields.forEach((field) => {
|
|
28
|
+
row[`texts.${locale}.${field}`] = t[field] ?? '';
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
const fetchTexts = async (modules, filterId, filterOptionValue) => {
|
|
33
|
+
const texts = await modules.filters.texts.findTexts({ filterId, filterOptionValue });
|
|
34
|
+
return texts.reduce((acc, t) => ({ ...acc, [t.locale]: t }), {});
|
|
35
|
+
};
|
|
36
|
+
const exportFiltersHandler = async ({ exportFilterOptions, exportFilters, ...params }, locales, unchainedAPI) => {
|
|
37
|
+
const { modules } = unchainedAPI;
|
|
38
|
+
const filters = await modules.filters.findFilters({ ...params });
|
|
39
|
+
const filterRows = [];
|
|
40
|
+
const optionRows = [];
|
|
41
|
+
for await (const filter of filters) {
|
|
42
|
+
if (exportFilters) {
|
|
43
|
+
const filterTexts = await fetchTexts(modules, filter._id);
|
|
44
|
+
const row = {
|
|
45
|
+
_id: filter._id,
|
|
46
|
+
key: filter.key,
|
|
47
|
+
type: filter.type,
|
|
48
|
+
isActive: filter.isActive,
|
|
49
|
+
meta: typeof filter?.meta === 'object' ? JSON.stringify(filter.meta) : '',
|
|
50
|
+
};
|
|
51
|
+
mapTextsToRow(row, filterTexts, locales);
|
|
52
|
+
filterRows.push(row);
|
|
53
|
+
}
|
|
54
|
+
if (exportFilterOptions) {
|
|
55
|
+
for await (const optionValue of filter.options) {
|
|
56
|
+
const optionTexts = await fetchTexts(modules, filter._id, optionValue);
|
|
57
|
+
const optionRow = {
|
|
58
|
+
optionId: `${filter._id}:${optionValue}`,
|
|
59
|
+
filterId: filter._id,
|
|
60
|
+
value: optionValue,
|
|
61
|
+
};
|
|
62
|
+
mapTextsToRow(optionRow, optionTexts, locales);
|
|
63
|
+
optionRows.push(optionRow);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const filtersCSV = exportFilters
|
|
68
|
+
? await generateCSVFileAndURL({
|
|
69
|
+
headers: buildFilterHeaders(locales),
|
|
70
|
+
rows: filterRows,
|
|
71
|
+
directoryName: EXPORTS_DIRECTORY,
|
|
72
|
+
fileName: 'filters_export.csv',
|
|
73
|
+
unchainedAPI,
|
|
74
|
+
})
|
|
75
|
+
: null;
|
|
76
|
+
const optionsCSV = exportFilterOptions
|
|
77
|
+
? await generateCSVFileAndURL({
|
|
78
|
+
headers: buildOptionHeaders(locales),
|
|
79
|
+
rows: optionRows,
|
|
80
|
+
directoryName: EXPORTS_DIRECTORY,
|
|
81
|
+
fileName: 'filter_options_export.csv',
|
|
82
|
+
unchainedAPI,
|
|
83
|
+
})
|
|
84
|
+
: null;
|
|
85
|
+
return { filters: filtersCSV, filterOptions: optionsCSV };
|
|
86
|
+
};
|
|
87
|
+
export default exportFiltersHandler;
|
|
88
|
+
exportFiltersHandler.payloadSchema = FilterExportPayloadSchema;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { UnchainedCore } from '../../core-index.ts';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
export declare const ProductExportPayloadSchema: z.ZodObject<{
|
|
4
|
+
exportProducts: z.ZodOptional<z.ZodBoolean>;
|
|
5
|
+
exportBundleItems: z.ZodOptional<z.ZodBoolean>;
|
|
6
|
+
exportPrices: z.ZodOptional<z.ZodBoolean>;
|
|
7
|
+
exportVariations: z.ZodOptional<z.ZodBoolean>;
|
|
8
|
+
exportVariationOptions: z.ZodOptional<z.ZodBoolean>;
|
|
9
|
+
queryString: z.ZodOptional<z.ZodString>;
|
|
10
|
+
includeDrafts: z.ZodOptional<z.ZodBoolean>;
|
|
11
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
12
|
+
}, z.core.$strip>;
|
|
13
|
+
export interface ProductExportParams {
|
|
14
|
+
exportProducts?: boolean;
|
|
15
|
+
exportBundleItems?: boolean;
|
|
16
|
+
exportPrices?: boolean;
|
|
17
|
+
exportVariations?: boolean;
|
|
18
|
+
exportVariationOptions?: boolean;
|
|
19
|
+
[key: string]: any;
|
|
20
|
+
}
|
|
21
|
+
declare const exportProductsHandler: {
|
|
22
|
+
({ exportBundleItems, exportPrices, exportProducts, exportVariationOptions, exportVariations, ...params }: ProductExportParams, locales: string[], unchainedAPI: UnchainedCore): Promise<{
|
|
23
|
+
products: import("./generateCSVFileAndUrl.ts").CSVFileResult | null;
|
|
24
|
+
prices: import("./generateCSVFileAndUrl.ts").CSVFileResult | null;
|
|
25
|
+
bundles: import("./generateCSVFileAndUrl.ts").CSVFileResult | null;
|
|
26
|
+
variations: import("./generateCSVFileAndUrl.ts").CSVFileResult | null;
|
|
27
|
+
variationOptions: import("./generateCSVFileAndUrl.ts").CSVFileResult | null;
|
|
28
|
+
}>;
|
|
29
|
+
payloadSchema: z.ZodObject<{
|
|
30
|
+
exportProducts: z.ZodOptional<z.ZodBoolean>;
|
|
31
|
+
exportBundleItems: z.ZodOptional<z.ZodBoolean>;
|
|
32
|
+
exportPrices: z.ZodOptional<z.ZodBoolean>;
|
|
33
|
+
exportVariations: z.ZodOptional<z.ZodBoolean>;
|
|
34
|
+
exportVariationOptions: z.ZodOptional<z.ZodBoolean>;
|
|
35
|
+
queryString: z.ZodOptional<z.ZodString>;
|
|
36
|
+
includeDrafts: z.ZodOptional<z.ZodBoolean>;
|
|
37
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
38
|
+
}, z.core.$strip>;
|
|
39
|
+
};
|
|
40
|
+
export default exportProductsHandler;
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import generateCSVFileAndURL from "./generateCSVFileAndUrl.js";
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { EXPORTS_DIRECTORY } from "../createBulkExporter.js";
|
|
4
|
+
export const ProductExportPayloadSchema = z.object({
|
|
5
|
+
exportProducts: z.boolean().optional(),
|
|
6
|
+
exportBundleItems: z.boolean().optional(),
|
|
7
|
+
exportPrices: z.boolean().optional(),
|
|
8
|
+
exportVariations: z.boolean().optional(),
|
|
9
|
+
exportVariationOptions: z.boolean().optional(),
|
|
10
|
+
queryString: z.string().optional(),
|
|
11
|
+
includeDrafts: z.boolean().optional(),
|
|
12
|
+
tags: z.array(z.string()).optional(),
|
|
13
|
+
});
|
|
14
|
+
const PRODUCT_CSV_SCHEMA = {
|
|
15
|
+
base: ['_id', 'sku', 'baseUnit', 'sequence', 'status', 'tags', 'updated', 'published', 'type'],
|
|
16
|
+
textFields: ['title', 'subtitle', 'description', 'vendor', 'brand', 'labels', 'slug'],
|
|
17
|
+
priceFields: [
|
|
18
|
+
'productId',
|
|
19
|
+
'amount',
|
|
20
|
+
'currencyCode',
|
|
21
|
+
'countryCode',
|
|
22
|
+
'isTaxable',
|
|
23
|
+
'isNetPrice',
|
|
24
|
+
'maxQuantity',
|
|
25
|
+
],
|
|
26
|
+
bundleItemHeaders: ['productId', 'bundleItemProductId', 'quantity', 'configuration'],
|
|
27
|
+
variationItemHeaders: ['productId', 'variationId', 'key', 'type'],
|
|
28
|
+
variationTextFields: ['title', 'subtitle'],
|
|
29
|
+
variationOptionItemHeaders: ['variationId', 'value'],
|
|
30
|
+
variationOptionTextFields: ['title', 'subtitle'],
|
|
31
|
+
};
|
|
32
|
+
const buildProductHeaders = (locales) => [
|
|
33
|
+
...PRODUCT_CSV_SCHEMA.base,
|
|
34
|
+
...locales.flatMap((l) => PRODUCT_CSV_SCHEMA.textFields.map((f) => `texts.${l}.${f}`)),
|
|
35
|
+
'supply.weightInGram',
|
|
36
|
+
'supply.heightInMillimeters',
|
|
37
|
+
'supply.lengthInMillimeters',
|
|
38
|
+
'supply.widthInMillimeters',
|
|
39
|
+
'meta',
|
|
40
|
+
];
|
|
41
|
+
const buildPriceHeaders = () => PRODUCT_CSV_SCHEMA.priceFields;
|
|
42
|
+
const buildBundleHeaders = () => PRODUCT_CSV_SCHEMA.bundleItemHeaders;
|
|
43
|
+
const buildVariationHeaders = (locales) => [
|
|
44
|
+
...PRODUCT_CSV_SCHEMA.variationItemHeaders,
|
|
45
|
+
...locales.flatMap((l) => PRODUCT_CSV_SCHEMA.variationTextFields.map((f) => `texts.${l}.${f}`)),
|
|
46
|
+
];
|
|
47
|
+
const buildVariationOptionsHeaders = (locales) => [
|
|
48
|
+
...PRODUCT_CSV_SCHEMA.variationOptionItemHeaders,
|
|
49
|
+
...locales.flatMap((l) => PRODUCT_CSV_SCHEMA.variationOptionTextFields.map((f) => `texts.${l}.${f}`)),
|
|
50
|
+
];
|
|
51
|
+
const mapTextsToRow = (row, texts, locales, fields) => {
|
|
52
|
+
locales.forEach((locale) => {
|
|
53
|
+
const t = texts.find((x) => x.locale === locale) || {};
|
|
54
|
+
fields.forEach((f) => {
|
|
55
|
+
row[`texts.${locale}.${f}`] = Array.isArray(t[f]) ? t[f].join(';') : (t[f] ?? '');
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
const buildProductRow = (product, locales) => {
|
|
60
|
+
const row = {};
|
|
61
|
+
PRODUCT_CSV_SCHEMA.base.forEach((k) => (row[k] = product[k] ?? ''));
|
|
62
|
+
row['supply.weightInGram'] = product?.dimensions?.weight ?? '';
|
|
63
|
+
row['supply.heightInMillimeters'] = product?.dimensions?.height ?? '';
|
|
64
|
+
row['supply.lengthInMillimeters'] = product?.dimensions?.length ?? '';
|
|
65
|
+
row['supply.widthInMillimeters'] = product?.dimensions?.width ?? '';
|
|
66
|
+
row['meta'] =
|
|
67
|
+
typeof product?.meta === 'object'
|
|
68
|
+
? JSON.stringify(product?.meta, Object.keys(product?.meta).sort())
|
|
69
|
+
: product?.meta;
|
|
70
|
+
mapTextsToRow(row, product.texts ?? [], locales, PRODUCT_CSV_SCHEMA.textFields);
|
|
71
|
+
return row;
|
|
72
|
+
};
|
|
73
|
+
const buildPriceRows = (productId, prices = []) => prices.map((p) => ({
|
|
74
|
+
productId,
|
|
75
|
+
amount: p.amount ?? '',
|
|
76
|
+
isNetPrice: p.isNetPrice ?? '',
|
|
77
|
+
isTaxable: p.isTaxable ?? '',
|
|
78
|
+
currencyCode: p.currency?.isoCode ?? '',
|
|
79
|
+
countryCode: p.country?.isoCode ?? '',
|
|
80
|
+
maxQuantity: p.maxQuantity ?? '',
|
|
81
|
+
}));
|
|
82
|
+
const buildBundleRows = (productId, bundles = []) => bundles.map((b) => ({
|
|
83
|
+
productId,
|
|
84
|
+
bundleItemProductId: b.productId,
|
|
85
|
+
quantity: b.quantity ?? 1,
|
|
86
|
+
configuration: (b.configuration || []).map((c) => Object.values(c).join(':')).join(';'),
|
|
87
|
+
}));
|
|
88
|
+
const buildVariationRows = (productId, variations = [], locales) => {
|
|
89
|
+
const variationRows = [];
|
|
90
|
+
const optionRows = [];
|
|
91
|
+
variations.forEach((v) => {
|
|
92
|
+
const row = {
|
|
93
|
+
productId,
|
|
94
|
+
variationId: v._id,
|
|
95
|
+
key: v.key,
|
|
96
|
+
type: v.type,
|
|
97
|
+
};
|
|
98
|
+
mapTextsToRow(row, Object.values(v.texts || {}), locales, PRODUCT_CSV_SCHEMA.variationTextFields);
|
|
99
|
+
variationRows.push(row);
|
|
100
|
+
const options = v.options?.[v._id] ?? [];
|
|
101
|
+
options.forEach((o) => {
|
|
102
|
+
const optRow = {
|
|
103
|
+
variationId: v._id,
|
|
104
|
+
value: o.productVariationOption,
|
|
105
|
+
};
|
|
106
|
+
mapTextsToRow(optRow, Object.values(o.texts || {}), locales, PRODUCT_CSV_SCHEMA.variationOptionTextFields);
|
|
107
|
+
optionRows.push(optRow);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
return { variationRows, optionRows };
|
|
111
|
+
};
|
|
112
|
+
const exportProductsHandler = async ({ exportBundleItems, exportPrices, exportProducts, exportVariationOptions, exportVariations, ...params }, locales, unchainedAPI) => {
|
|
113
|
+
const { queryString, includeDrafts, tags } = params;
|
|
114
|
+
const products = await unchainedAPI.modules.products.findProducts({
|
|
115
|
+
includeDrafts,
|
|
116
|
+
queryString,
|
|
117
|
+
tags,
|
|
118
|
+
});
|
|
119
|
+
const normalized = { products: {}, prices: {}, bundles: {}, variations: {} };
|
|
120
|
+
for await (const p of products) {
|
|
121
|
+
const productId = p._id;
|
|
122
|
+
const productTexts = exportProducts
|
|
123
|
+
? await unchainedAPI.modules.products.texts.findTexts({ productId })
|
|
124
|
+
: null;
|
|
125
|
+
const variations = exportVariations
|
|
126
|
+
? await unchainedAPI.modules.products.variations.findProductVariations({ productId })
|
|
127
|
+
: [];
|
|
128
|
+
const normalizedVariations = [];
|
|
129
|
+
if (exportVariations) {
|
|
130
|
+
for await (const v of variations) {
|
|
131
|
+
const variationTexts = exportVariations
|
|
132
|
+
? await unchainedAPI.modules.products.variations.texts.findVariationTexts({
|
|
133
|
+
productVariationId: v._id,
|
|
134
|
+
productVariationOptionValue: null,
|
|
135
|
+
})
|
|
136
|
+
: [];
|
|
137
|
+
const options = [];
|
|
138
|
+
if (exportVariationOptions) {
|
|
139
|
+
for await (const o of v.options || []) {
|
|
140
|
+
const optionTexts = await unchainedAPI.modules.products.variations.texts.findVariationTexts({
|
|
141
|
+
productVariationId: v._id,
|
|
142
|
+
productVariationOptionValue: o,
|
|
143
|
+
});
|
|
144
|
+
options.push({
|
|
145
|
+
_id: `${v._id}:${o}`,
|
|
146
|
+
productVariationOption: o,
|
|
147
|
+
texts: Object.fromEntries(optionTexts.map((t) => [t.locale, t])),
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
normalizedVariations.push({
|
|
152
|
+
...v,
|
|
153
|
+
texts: exportVariations
|
|
154
|
+
? Object.fromEntries(variationTexts.map((t) => [t.locale, t]))
|
|
155
|
+
: {},
|
|
156
|
+
options: exportVariationOptions ? { [v._id]: options } : {},
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
normalized.products[productId] = exportProducts ? { ...p, texts: productTexts } : p;
|
|
161
|
+
normalized.prices[productId] = exportPrices ? (p.commerce?.pricing ?? []) : [];
|
|
162
|
+
normalized.bundles[productId] = exportBundleItems ? (p.bundleItems ?? []) : [];
|
|
163
|
+
normalized.variations[productId] = normalizedVariations;
|
|
164
|
+
}
|
|
165
|
+
const productRows = [];
|
|
166
|
+
const priceRows = [];
|
|
167
|
+
const bundleRows = [];
|
|
168
|
+
const variationRows = [];
|
|
169
|
+
const variationOptionRows = [];
|
|
170
|
+
for (const pid in normalized.products) {
|
|
171
|
+
if (exportProducts) {
|
|
172
|
+
productRows.push(buildProductRow(normalized.products[pid], locales));
|
|
173
|
+
}
|
|
174
|
+
if (exportPrices) {
|
|
175
|
+
priceRows.push(...buildPriceRows(pid, normalized.prices[pid]));
|
|
176
|
+
}
|
|
177
|
+
if (exportBundleItems) {
|
|
178
|
+
bundleRows.push(...buildBundleRows(pid, normalized.bundles[pid]));
|
|
179
|
+
}
|
|
180
|
+
const { variationRows: vr, optionRows: or } = buildVariationRows(pid, normalized.variations[pid], locales);
|
|
181
|
+
variationRows.push(...vr);
|
|
182
|
+
variationOptionRows.push(...or);
|
|
183
|
+
}
|
|
184
|
+
const productsCSV = exportProducts
|
|
185
|
+
? await generateCSVFileAndURL({
|
|
186
|
+
headers: buildProductHeaders(locales),
|
|
187
|
+
rows: productRows,
|
|
188
|
+
directoryName: EXPORTS_DIRECTORY,
|
|
189
|
+
fileName: 'products_export.csv',
|
|
190
|
+
unchainedAPI,
|
|
191
|
+
})
|
|
192
|
+
: null;
|
|
193
|
+
const pricesCSV = exportPrices
|
|
194
|
+
? await generateCSVFileAndURL({
|
|
195
|
+
headers: buildPriceHeaders(),
|
|
196
|
+
rows: priceRows,
|
|
197
|
+
directoryName: EXPORTS_DIRECTORY,
|
|
198
|
+
fileName: 'products_prices_export.csv',
|
|
199
|
+
unchainedAPI,
|
|
200
|
+
})
|
|
201
|
+
: null;
|
|
202
|
+
const bundlesCSV = exportBundleItems
|
|
203
|
+
? await generateCSVFileAndURL({
|
|
204
|
+
headers: buildBundleHeaders(),
|
|
205
|
+
rows: bundleRows,
|
|
206
|
+
directoryName: EXPORTS_DIRECTORY,
|
|
207
|
+
fileName: 'products_bundle_items_export.csv',
|
|
208
|
+
unchainedAPI,
|
|
209
|
+
})
|
|
210
|
+
: null;
|
|
211
|
+
const variationsCSV = exportVariations
|
|
212
|
+
? await generateCSVFileAndURL({
|
|
213
|
+
headers: buildVariationHeaders(locales),
|
|
214
|
+
rows: variationRows,
|
|
215
|
+
directoryName: EXPORTS_DIRECTORY,
|
|
216
|
+
fileName: 'products_variations_export.csv',
|
|
217
|
+
unchainedAPI,
|
|
218
|
+
})
|
|
219
|
+
: null;
|
|
220
|
+
const variationOptionsCSV = exportVariationOptions
|
|
221
|
+
? await generateCSVFileAndURL({
|
|
222
|
+
headers: buildVariationOptionsHeaders(locales),
|
|
223
|
+
rows: variationOptionRows,
|
|
224
|
+
directoryName: EXPORTS_DIRECTORY,
|
|
225
|
+
fileName: 'products_variation_options_export.csv',
|
|
226
|
+
unchainedAPI,
|
|
227
|
+
})
|
|
228
|
+
: null;
|
|
229
|
+
return {
|
|
230
|
+
products: productsCSV,
|
|
231
|
+
prices: pricesCSV,
|
|
232
|
+
bundles: bundlesCSV,
|
|
233
|
+
variations: variationsCSV,
|
|
234
|
+
variationOptions: variationOptionsCSV,
|
|
235
|
+
};
|
|
236
|
+
};
|
|
237
|
+
export default exportProductsHandler;
|
|
238
|
+
exportProductsHandler.payloadSchema = ProductExportPayloadSchema;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { UnchainedCore } from '../../core-index.ts';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
export declare const UserExportPayloadSchema: z.ZodObject<{
|
|
4
|
+
exportReviews: z.ZodOptional<z.ZodBoolean>;
|
|
5
|
+
exportOrders: z.ZodOptional<z.ZodBoolean>;
|
|
6
|
+
exportBookmarks: z.ZodOptional<z.ZodBoolean>;
|
|
7
|
+
exportEvents: z.ZodOptional<z.ZodBoolean>;
|
|
8
|
+
exportQuotations: z.ZodOptional<z.ZodBoolean>;
|
|
9
|
+
exportEnrollments: z.ZodOptional<z.ZodBoolean>;
|
|
10
|
+
userId: z.ZodOptional<z.ZodString>;
|
|
11
|
+
}, z.core.$strip>;
|
|
12
|
+
export interface UserExportParams {
|
|
13
|
+
exportReviews?: boolean;
|
|
14
|
+
exportOrders?: boolean;
|
|
15
|
+
exportBookmarks?: boolean;
|
|
16
|
+
exportEvents?: boolean;
|
|
17
|
+
exportQuotations?: boolean;
|
|
18
|
+
exportEnrollments?: boolean;
|
|
19
|
+
userId: string;
|
|
20
|
+
}
|
|
21
|
+
declare const exportUsersHandler: {
|
|
22
|
+
({ userId, ...options }: UserExportParams, _: any, unchainedAPI: UnchainedCore): Promise<{
|
|
23
|
+
user: import("./generateCSVFileAndUrl.ts").CSVFileResult;
|
|
24
|
+
bookmarks: import("./generateCSVFileAndUrl.ts").CSVFileResult | null;
|
|
25
|
+
orders: import("./generateCSVFileAndUrl.ts").CSVFileResult | null;
|
|
26
|
+
reviews: import("./generateCSVFileAndUrl.ts").CSVFileResult | null;
|
|
27
|
+
quotations: import("./generateCSVFileAndUrl.ts").CSVFileResult | null;
|
|
28
|
+
enrollments: import("./generateCSVFileAndUrl.ts").CSVFileResult | null;
|
|
29
|
+
}>;
|
|
30
|
+
payloadSchema: z.ZodObject<{
|
|
31
|
+
exportReviews: z.ZodOptional<z.ZodBoolean>;
|
|
32
|
+
exportOrders: z.ZodOptional<z.ZodBoolean>;
|
|
33
|
+
exportBookmarks: z.ZodOptional<z.ZodBoolean>;
|
|
34
|
+
exportEvents: z.ZodOptional<z.ZodBoolean>;
|
|
35
|
+
exportQuotations: z.ZodOptional<z.ZodBoolean>;
|
|
36
|
+
exportEnrollments: z.ZodOptional<z.ZodBoolean>;
|
|
37
|
+
userId: z.ZodOptional<z.ZodString>;
|
|
38
|
+
}, z.core.$strip>;
|
|
39
|
+
};
|
|
40
|
+
export default exportUsersHandler;
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import generateCSVFileAndURL from "./generateCSVFileAndUrl.js";
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { EXPORTS_DIRECTORY } from "../createBulkExporter.js";
|
|
4
|
+
export const UserExportPayloadSchema = z.object({
|
|
5
|
+
exportReviews: z.boolean().optional(),
|
|
6
|
+
exportOrders: z.boolean().optional(),
|
|
7
|
+
exportBookmarks: z.boolean().optional(),
|
|
8
|
+
exportEvents: z.boolean().optional(),
|
|
9
|
+
exportQuotations: z.boolean().optional(),
|
|
10
|
+
exportEnrollments: z.boolean().optional(),
|
|
11
|
+
userId: z.string().optional(),
|
|
12
|
+
});
|
|
13
|
+
const USER_CSV_SCHEMA = {
|
|
14
|
+
userFields: [
|
|
15
|
+
'_id',
|
|
16
|
+
'emailAddresses',
|
|
17
|
+
'tags',
|
|
18
|
+
'roles',
|
|
19
|
+
'username',
|
|
20
|
+
'created',
|
|
21
|
+
'isGuest',
|
|
22
|
+
'displayName',
|
|
23
|
+
'birthday',
|
|
24
|
+
'phoneMobile',
|
|
25
|
+
'gender',
|
|
26
|
+
'address.addressLine',
|
|
27
|
+
'address.addressLine2',
|
|
28
|
+
'address.city',
|
|
29
|
+
'address.company',
|
|
30
|
+
'address.countryCode',
|
|
31
|
+
'address.firstName',
|
|
32
|
+
'address.lastName',
|
|
33
|
+
'address.postalCode',
|
|
34
|
+
'address.regionCode',
|
|
35
|
+
'meta',
|
|
36
|
+
'lastBillingAddress',
|
|
37
|
+
'lastContact',
|
|
38
|
+
'lastLogin',
|
|
39
|
+
],
|
|
40
|
+
bookmarkFields: ['_id', 'productId', 'userId'],
|
|
41
|
+
orderFields: [
|
|
42
|
+
'_id',
|
|
43
|
+
'userId',
|
|
44
|
+
'orderNumber',
|
|
45
|
+
'status',
|
|
46
|
+
'billingAddress',
|
|
47
|
+
'contact',
|
|
48
|
+
'countryCode',
|
|
49
|
+
'currencyCode',
|
|
50
|
+
'deliveryId',
|
|
51
|
+
'paymentId',
|
|
52
|
+
'confirmed',
|
|
53
|
+
'ordered',
|
|
54
|
+
'fulfilled',
|
|
55
|
+
'products',
|
|
56
|
+
],
|
|
57
|
+
reviewFields: [
|
|
58
|
+
'_id',
|
|
59
|
+
'productId',
|
|
60
|
+
'authorId',
|
|
61
|
+
'rating',
|
|
62
|
+
'title',
|
|
63
|
+
'review',
|
|
64
|
+
'vote.type',
|
|
65
|
+
'vote.timestamp',
|
|
66
|
+
'vote.meta',
|
|
67
|
+
'meta',
|
|
68
|
+
],
|
|
69
|
+
quotationFields: [
|
|
70
|
+
'_id',
|
|
71
|
+
'userId',
|
|
72
|
+
'quotationNumber',
|
|
73
|
+
'productId',
|
|
74
|
+
'status',
|
|
75
|
+
'price',
|
|
76
|
+
'expires',
|
|
77
|
+
'fulfilled',
|
|
78
|
+
'rejected',
|
|
79
|
+
'deleted',
|
|
80
|
+
'meta',
|
|
81
|
+
'configuration',
|
|
82
|
+
],
|
|
83
|
+
enrollmentFields: [
|
|
84
|
+
'_id',
|
|
85
|
+
'userId',
|
|
86
|
+
'productId',
|
|
87
|
+
'enrollmentNumber',
|
|
88
|
+
'status',
|
|
89
|
+
'countryCode',
|
|
90
|
+
'currencyCode',
|
|
91
|
+
'quantity',
|
|
92
|
+
'created',
|
|
93
|
+
'deleted',
|
|
94
|
+
'expires',
|
|
95
|
+
'configuration',
|
|
96
|
+
'billingAddress',
|
|
97
|
+
'contact.emailAddress',
|
|
98
|
+
'contact.telNumber',
|
|
99
|
+
'delivery.providerId',
|
|
100
|
+
'payment.providerId',
|
|
101
|
+
'delivery.meta',
|
|
102
|
+
'payment.meta',
|
|
103
|
+
'meta',
|
|
104
|
+
],
|
|
105
|
+
};
|
|
106
|
+
const exportUsersHandler = async ({ userId, ...options }, _, unchainedAPI) => {
|
|
107
|
+
const { modules } = unchainedAPI;
|
|
108
|
+
const user = await modules.users.findUserById(userId);
|
|
109
|
+
const userRows = [];
|
|
110
|
+
const orderRows = [];
|
|
111
|
+
const bookmarkRows = [];
|
|
112
|
+
const quotationRows = [];
|
|
113
|
+
const reviewRows = [];
|
|
114
|
+
const enrollmentRows = [];
|
|
115
|
+
if (!user)
|
|
116
|
+
throw new Error(`User with ID ${userId} not found`);
|
|
117
|
+
userRows.push({
|
|
118
|
+
_id: user._id,
|
|
119
|
+
emailAddresses: user.emails ? user.emails.map((email) => email.address).join('; ') : '',
|
|
120
|
+
created: new Date(user.created).getTime(),
|
|
121
|
+
isGuest: user.guest || false,
|
|
122
|
+
tags: user.tags ? user.tags.join('; ') : '',
|
|
123
|
+
roles: user.roles ? user.roles.join('; ') : '',
|
|
124
|
+
username: user.username || '',
|
|
125
|
+
displayName: user.profile?.displayName || '',
|
|
126
|
+
birthday: user.profile?.birthday ? new Date(user.profile?.birthday).getTime() : '',
|
|
127
|
+
phoneMobile: user.profile?.phoneMobile || '',
|
|
128
|
+
gender: user.profile?.gender || '',
|
|
129
|
+
'address.addressLine': user.profile?.address?.addressLine || '',
|
|
130
|
+
'address.addressLine2': user.profile?.address?.addressLine2 || '',
|
|
131
|
+
'address.city': user.profile?.address?.city || '',
|
|
132
|
+
'address.company': user.profile?.address?.company || '',
|
|
133
|
+
'address.countryCode': user.profile?.address?.countryCode || '',
|
|
134
|
+
'address.firstName': user.profile?.address?.firstName || '',
|
|
135
|
+
'address.lastName': user.profile?.address?.lastName || '',
|
|
136
|
+
'address.postalCode': user.profile?.address?.postalCode || '',
|
|
137
|
+
'address.regionCode': user.profile?.address?.regionCode || '',
|
|
138
|
+
meta: user.meta ? JSON.stringify(user.meta) : '',
|
|
139
|
+
lastBillingAddress: user.lastBillingAddress ? JSON.stringify(user.lastBillingAddress) : '',
|
|
140
|
+
lastContact: user.lastContact ? JSON.stringify(user.lastContact) : '',
|
|
141
|
+
lastLogin: user.lastLogin ? JSON.stringify(user.lastLogin) : '',
|
|
142
|
+
});
|
|
143
|
+
if (options.exportBookmarks) {
|
|
144
|
+
const bookmarks = await modules.bookmarks.findBookmarksByUserId(userId);
|
|
145
|
+
for (const bookmark of bookmarks) {
|
|
146
|
+
const row = {};
|
|
147
|
+
USER_CSV_SCHEMA.bookmarkFields.forEach((field) => {
|
|
148
|
+
row[field] = bookmark[field] || '';
|
|
149
|
+
});
|
|
150
|
+
bookmarkRows.push(row);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (options.exportOrders) {
|
|
154
|
+
const orders = await modules.orders.findOrders({ userId });
|
|
155
|
+
for await (const order of orders) {
|
|
156
|
+
const positions = await modules.orders.positions.findOrderPositions({ orderId: order._id });
|
|
157
|
+
const row = {};
|
|
158
|
+
USER_CSV_SCHEMA.orderFields.forEach((field) => {
|
|
159
|
+
if ((field === 'ordered' || field === 'confirmed' || field === 'fulfilled') && order[field]) {
|
|
160
|
+
row[field] = new Date(order[field]).getTime();
|
|
161
|
+
}
|
|
162
|
+
else if (field === 'products') {
|
|
163
|
+
row[field] = positions.map((pos) => `${pos.productId}~${pos.quantity}`).join('; ');
|
|
164
|
+
}
|
|
165
|
+
else if (field === 'billingAddress' && order.billingAddress) {
|
|
166
|
+
row[field] = JSON.stringify(order.billingAddress);
|
|
167
|
+
}
|
|
168
|
+
else if (field === 'contact' && order.contact) {
|
|
169
|
+
row[field] = JSON.stringify(order.contact);
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
row[field] = order[field] || '';
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
orderRows.push(row);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (options.exportReviews) {
|
|
179
|
+
const reviews = await modules.products.reviews.findProductReviews({
|
|
180
|
+
authorId: userId,
|
|
181
|
+
});
|
|
182
|
+
for (const review of reviews) {
|
|
183
|
+
const row = {};
|
|
184
|
+
USER_CSV_SCHEMA.reviewFields.forEach((field) => {
|
|
185
|
+
if (field.startsWith('vote.')) {
|
|
186
|
+
const voteField = field.split('.')[1];
|
|
187
|
+
if (voteField === 'timestamp' && review.votes[0][voteField]) {
|
|
188
|
+
row[field] = new Date(review.votes[0][voteField]).getTime();
|
|
189
|
+
}
|
|
190
|
+
else if (voteField === 'meta' && review.votes[0][voteField]) {
|
|
191
|
+
row[field] = JSON.stringify(review.votes[0][voteField]);
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
row[field] = review.votes[0][voteField] || '';
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
if (field === 'meta' && review.meta) {
|
|
199
|
+
row[field] = JSON.stringify(review.meta);
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
row[field] = review[field] || '';
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
reviewRows.push(row);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (options.exportQuotations) {
|
|
210
|
+
const quotations = await modules.quotations.findQuotations({
|
|
211
|
+
userId,
|
|
212
|
+
});
|
|
213
|
+
for (const quotation of quotations) {
|
|
214
|
+
const row = {};
|
|
215
|
+
USER_CSV_SCHEMA.quotationFields.forEach((field) => {
|
|
216
|
+
if ((field === 'expires' ||
|
|
217
|
+
field === 'fulfilled' ||
|
|
218
|
+
field === 'rejected' ||
|
|
219
|
+
field === 'deleted') &&
|
|
220
|
+
quotation[field]) {
|
|
221
|
+
row[field] = new Date(quotation[field]).getTime();
|
|
222
|
+
}
|
|
223
|
+
else if (field === 'configuration' || (field === 'meta' && quotation[field])) {
|
|
224
|
+
row[field] = JSON.stringify(quotation.configuration);
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
row[field] = quotation[field] || '';
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
quotationRows.push(row);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (options.exportEnrollments) {
|
|
234
|
+
const enrollments = await modules.enrollments.findEnrollments({
|
|
235
|
+
userId,
|
|
236
|
+
});
|
|
237
|
+
for (const enrollment of enrollments) {
|
|
238
|
+
const row = {};
|
|
239
|
+
USER_CSV_SCHEMA.enrollmentFields.forEach((field) => {
|
|
240
|
+
if ((field === 'created' || field === 'deleted' || field === 'expires') && enrollment[field]) {
|
|
241
|
+
row[field] = new Date(enrollment[field]).getTime();
|
|
242
|
+
}
|
|
243
|
+
else if (field === 'billingAddress' && enrollment[field]) {
|
|
244
|
+
row[field] = JSON.stringify(enrollment[field]);
|
|
245
|
+
}
|
|
246
|
+
else if (field === 'contact.emailAddress' && enrollment.contact) {
|
|
247
|
+
row[field] = enrollment.contact.emailAddress || '';
|
|
248
|
+
}
|
|
249
|
+
else if (field === 'contact.telNumber' && enrollment.contact) {
|
|
250
|
+
row[field] = enrollment.contact.telNumber || '';
|
|
251
|
+
}
|
|
252
|
+
else if (field === 'delivery.providerId' && enrollment.delivery) {
|
|
253
|
+
row[field] = enrollment.delivery.deliveryProviderId || '';
|
|
254
|
+
}
|
|
255
|
+
else if (field === 'payment.providerId' && enrollment.payment) {
|
|
256
|
+
row[field] = enrollment.payment.paymentProviderId || '';
|
|
257
|
+
}
|
|
258
|
+
else if (field === 'delivery.meta' && enrollment.delivery) {
|
|
259
|
+
row[field] = enrollment.delivery.meta
|
|
260
|
+
? JSON.stringify(enrollment.delivery.meta)
|
|
261
|
+
: '';
|
|
262
|
+
}
|
|
263
|
+
else if (field === 'payment.meta' && enrollment.payment) {
|
|
264
|
+
row[field] = enrollment.payment.meta
|
|
265
|
+
? JSON.stringify(enrollment.payment.meta)
|
|
266
|
+
: '';
|
|
267
|
+
}
|
|
268
|
+
else if (field === 'configuration' || (field === 'meta' && enrollment[field])) {
|
|
269
|
+
row[field] = JSON.stringify(enrollment.configuration);
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
row[field] = enrollment[field] || '';
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
enrollmentRows.push(row);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
const userCSV = await generateCSVFileAndURL({
|
|
279
|
+
headers: USER_CSV_SCHEMA.userFields,
|
|
280
|
+
rows: userRows,
|
|
281
|
+
directoryName: EXPORTS_DIRECTORY,
|
|
282
|
+
fileName: 'user_export.csv',
|
|
283
|
+
unchainedAPI,
|
|
284
|
+
});
|
|
285
|
+
const reviewCSV = options.exportReviews
|
|
286
|
+
? await generateCSVFileAndURL({
|
|
287
|
+
headers: USER_CSV_SCHEMA.reviewFields,
|
|
288
|
+
rows: reviewRows,
|
|
289
|
+
directoryName: EXPORTS_DIRECTORY,
|
|
290
|
+
fileName: 'user_reviews_export.csv',
|
|
291
|
+
unchainedAPI,
|
|
292
|
+
})
|
|
293
|
+
: null;
|
|
294
|
+
const quotationCSV = options.exportQuotations
|
|
295
|
+
? await generateCSVFileAndURL({
|
|
296
|
+
headers: USER_CSV_SCHEMA.quotationFields,
|
|
297
|
+
rows: quotationRows,
|
|
298
|
+
directoryName: EXPORTS_DIRECTORY,
|
|
299
|
+
fileName: 'user_quotations_export.csv',
|
|
300
|
+
unchainedAPI,
|
|
301
|
+
})
|
|
302
|
+
: null;
|
|
303
|
+
const bookmarksCSV = options.exportBookmarks
|
|
304
|
+
? await generateCSVFileAndURL({
|
|
305
|
+
headers: USER_CSV_SCHEMA.bookmarkFields,
|
|
306
|
+
rows: bookmarkRows,
|
|
307
|
+
directoryName: EXPORTS_DIRECTORY,
|
|
308
|
+
fileName: 'user_bookmarks_export.csv',
|
|
309
|
+
unchainedAPI,
|
|
310
|
+
})
|
|
311
|
+
: null;
|
|
312
|
+
const ordersCSV = options.exportOrders
|
|
313
|
+
? await generateCSVFileAndURL({
|
|
314
|
+
headers: USER_CSV_SCHEMA.orderFields,
|
|
315
|
+
rows: orderRows,
|
|
316
|
+
directoryName: EXPORTS_DIRECTORY,
|
|
317
|
+
fileName: 'user_orders_export.csv',
|
|
318
|
+
unchainedAPI,
|
|
319
|
+
})
|
|
320
|
+
: null;
|
|
321
|
+
const enrollmentCSV = options.exportEnrollments
|
|
322
|
+
? await generateCSVFileAndURL({
|
|
323
|
+
headers: USER_CSV_SCHEMA.enrollmentFields,
|
|
324
|
+
rows: enrollmentRows,
|
|
325
|
+
directoryName: EXPORTS_DIRECTORY,
|
|
326
|
+
fileName: 'user_enrollments_export.csv',
|
|
327
|
+
unchainedAPI,
|
|
328
|
+
})
|
|
329
|
+
: null;
|
|
330
|
+
return {
|
|
331
|
+
user: userCSV,
|
|
332
|
+
bookmarks: bookmarksCSV,
|
|
333
|
+
orders: ordersCSV,
|
|
334
|
+
reviews: reviewCSV,
|
|
335
|
+
quotations: quotationCSV,
|
|
336
|
+
enrollments: enrollmentCSV,
|
|
337
|
+
};
|
|
338
|
+
};
|
|
339
|
+
export default exportUsersHandler;
|
|
340
|
+
exportUsersHandler.payloadSchema = UserExportPayloadSchema;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { UnchainedCore } from '../../core-index.ts';
|
|
2
|
+
export interface CSVFileResult {
|
|
3
|
+
url: string;
|
|
4
|
+
expires: number;
|
|
5
|
+
}
|
|
6
|
+
declare const generateCSVFileAndURL: ({ rows, headers, directoryName, fileName, unchainedAPI, }: {
|
|
7
|
+
rows: Record<string, unknown>[];
|
|
8
|
+
headers: string[];
|
|
9
|
+
directoryName: string;
|
|
10
|
+
fileName: string;
|
|
11
|
+
unchainedAPI: UnchainedCore;
|
|
12
|
+
}, expires?: number) => Promise<CSVFileResult>;
|
|
13
|
+
export default generateCSVFileAndURL;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import toCSV from "./toCSV.js";
|
|
2
|
+
const generateCSVFileAndURL = async ({ rows, headers, directoryName, fileName, unchainedAPI, }, expires = 3600000) => {
|
|
3
|
+
if (!rows.length)
|
|
4
|
+
return { url: '', expires: 0 };
|
|
5
|
+
const csvString = toCSV(headers, rows);
|
|
6
|
+
const uploaded = await unchainedAPI.services.files.uploadFileFromStream({
|
|
7
|
+
directoryName,
|
|
8
|
+
rawFile: { filename: fileName, buffer: Buffer.from(csvString).toString('base64') },
|
|
9
|
+
meta: { isPrivate: true },
|
|
10
|
+
});
|
|
11
|
+
const expiresAt = Date.now() + expires;
|
|
12
|
+
const url = await unchainedAPI.services.files.createFileDownloadURL({
|
|
13
|
+
file: uploaded,
|
|
14
|
+
expires: expiresAt,
|
|
15
|
+
});
|
|
16
|
+
if (!url) {
|
|
17
|
+
throw new Error(`Failed to generate download URL for ${fileName}`);
|
|
18
|
+
}
|
|
19
|
+
return { url, expires: expiresAt };
|
|
20
|
+
};
|
|
21
|
+
export default generateCSVFileAndURL;
|
package/lib/core-index.d.ts
CHANGED
|
@@ -3,10 +3,12 @@ import { type CustomServices, type Services } from './services/index.ts';
|
|
|
3
3
|
import { type Modules, type ModuleOptions } from './modules.ts';
|
|
4
4
|
import { type BulkImporter, type BulkImportHandler } from './bulk-importer/index.ts';
|
|
5
5
|
import type { IBaseAdapter } from '@unchainedshop/utils';
|
|
6
|
+
import { type BulkExporter, type BulkExportHandler } from './bulk-exporter/index.ts';
|
|
6
7
|
export * from './bulk-importer/index.ts';
|
|
7
8
|
export * from './services/index.ts';
|
|
8
9
|
export * from './directors/index.ts';
|
|
9
10
|
export * from './factory/index.ts';
|
|
11
|
+
export * from './bulk-exporter/index.ts';
|
|
10
12
|
export { default as schedule, type ScheduleData } from './utils/schedule.ts';
|
|
11
13
|
export interface UnchainedCoreOptions {
|
|
12
14
|
db: mongodb.Db;
|
|
@@ -14,6 +16,9 @@ export interface UnchainedCoreOptions {
|
|
|
14
16
|
bulkImporter?: {
|
|
15
17
|
handlers?: Record<string, BulkImportHandler<UnchainedCore>>;
|
|
16
18
|
};
|
|
19
|
+
bulkExporter?: {
|
|
20
|
+
handlers?: Record<string, BulkExportHandler<UnchainedCore>>;
|
|
21
|
+
};
|
|
17
22
|
modules?: Record<string, {
|
|
18
23
|
configure: (params: ModuleInput<any>) => any;
|
|
19
24
|
}>;
|
|
@@ -25,6 +30,7 @@ export interface UnchainedCore {
|
|
|
25
30
|
services: Services;
|
|
26
31
|
bulkImporter: BulkImporter;
|
|
27
32
|
options: ModuleOptions;
|
|
33
|
+
bulkExporter: BulkExporter;
|
|
28
34
|
}
|
|
29
|
-
export declare const initCore: ({ db, migrationRepository, bulkImporter: bulkImporterOptions, modules: customModules, services: customServices, options, }: UnchainedCoreOptions) => Promise<UnchainedCore>;
|
|
35
|
+
export declare const initCore: ({ db, migrationRepository, bulkImporter: bulkImporterOptions, modules: customModules, services: customServices, options, bulkExporter: bulkExporterOptions, }: UnchainedCoreOptions) => Promise<UnchainedCore>;
|
|
30
36
|
export declare const getAllAdapters: () => IBaseAdapter[];
|
package/lib/core-index.js
CHANGED
|
@@ -3,13 +3,16 @@ import initServices, {} from "./services/index.js";
|
|
|
3
3
|
import initModules, {} from "./modules.js";
|
|
4
4
|
import createBulkImporterFactory, {} from "./bulk-importer/index.js";
|
|
5
5
|
import { WorkerDirector, DeliveryDirector, DeliveryPricingDirector, EnrollmentDirector, FilterDirector, OrderDiscountDirector, OrderPricingDirector, PaymentDirector, PaymentPricingDirector, ProductDiscountDirector, ProductPricingDirector, QuotationDirector, WarehousingDirector, } from "./directors/index.js";
|
|
6
|
+
import createBulkExporterFactory, {} from "./bulk-exporter/index.js";
|
|
6
7
|
export * from "./bulk-importer/index.js";
|
|
7
8
|
export * from "./services/index.js";
|
|
8
9
|
export * from "./directors/index.js";
|
|
9
10
|
export * from "./factory/index.js";
|
|
11
|
+
export * from "./bulk-exporter/index.js";
|
|
10
12
|
export { default as schedule } from "./utils/schedule.js";
|
|
11
|
-
export const initCore = async ({ db, migrationRepository, bulkImporter: bulkImporterOptions = {}, modules: customModules = {}, services: customServices = {}, options = {}, }) => {
|
|
13
|
+
export const initCore = async ({ db, migrationRepository, bulkImporter: bulkImporterOptions = {}, modules: customModules = {}, services: customServices = {}, options = {}, bulkExporter: bulkExporterOptions = {}, }) => {
|
|
12
14
|
const bulkImporter = createBulkImporterFactory(db, bulkImporterOptions);
|
|
15
|
+
const bulkExporter = createBulkExporterFactory(bulkExporterOptions);
|
|
13
16
|
const modules = await initModules({ db, migrationRepository, options }, customModules);
|
|
14
17
|
const services = initServices(modules, customServices);
|
|
15
18
|
return {
|
|
@@ -17,6 +20,7 @@ export const initCore = async ({ db, migrationRepository, bulkImporter: bulkImpo
|
|
|
17
20
|
services,
|
|
18
21
|
bulkImporter,
|
|
19
22
|
options,
|
|
23
|
+
bulkExporter,
|
|
20
24
|
};
|
|
21
25
|
};
|
|
22
26
|
export const getAllAdapters = () => {
|
|
@@ -3,6 +3,7 @@ import type { WorkResult } from '@unchainedshop/core-worker';
|
|
|
3
3
|
import type { ModuleOptions, Modules } from '../modules.ts';
|
|
4
4
|
import type { Services } from '../services/index.ts';
|
|
5
5
|
import type { BulkImporter } from '../bulk-importer/index.ts';
|
|
6
|
+
import type { BulkExporter } from '../bulk-exporter/index.ts';
|
|
6
7
|
export type IWorkerAdapter<Input, Output> = IBaseAdapter & {
|
|
7
8
|
type: string;
|
|
8
9
|
external: boolean;
|
|
@@ -12,6 +13,7 @@ export type IWorkerAdapter<Input, Output> = IBaseAdapter & {
|
|
|
12
13
|
services: Services;
|
|
13
14
|
bulkImporter: BulkImporter;
|
|
14
15
|
options: ModuleOptions;
|
|
16
|
+
bulkExporter: BulkExporter;
|
|
15
17
|
}, workId: string) => Promise<WorkResult<Output>>;
|
|
16
18
|
};
|
|
17
19
|
export declare const WorkerAdapter: Omit<IWorkerAdapter<any, void>, 'key' | 'label' | 'type' | 'version'>;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unchainedshop/core",
|
|
3
3
|
"description": "Core orchestration package for the Unchained Engine with business services and directors",
|
|
4
|
-
"version": "4.6.
|
|
4
|
+
"version": "4.6.1",
|
|
5
5
|
"main": "lib/core-index.js",
|
|
6
6
|
"types": "lib/core-index.d.ts",
|
|
7
7
|
"type": "module",
|