bb-inspired
Version:
Core library for BB-inspired NestJS backend
452 lines • 18.9 kB
JavaScript
;
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var DataTransferService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.DataTransferService = void 0;
const common_1 = require("@nestjs/common");
const axios_1 = require("@nestjs/axios");
const fs = require("fs");
const path = require("path");
const crypto = require("crypto");
const util_1 = require("util");
const rxjs_1 = require("rxjs");
const logger_1 = require("../../utils/logger");
let DataTransferService = DataTransferService_1 = class DataTransferService {
constructor(options = {}, httpService) {
this.options = options;
this.httpService = httpService;
this.logger = new logger_1.AppLogger(DataTransferService_1.name);
this.defaultOptions = {
basePath: './exports',
defaultFormat: 'json',
encryption: {
algorithm: 'aes-256-cbc',
},
compression: {
enabled: false,
},
validation: {
enabled: true,
strictMode: false,
},
};
this.options = { ...this.defaultOptions, ...options };
if (this.options.basePath) {
if (!fs.existsSync(this.options.basePath)) {
fs.mkdirSync(this.options.basePath, { recursive: true });
}
}
}
async exportData(data, options = {}) {
var _a, _b;
try {
const timestamp = new Date().toISOString();
const format = options.format || this.options.defaultFormat;
const metadata = {
version: '1.0',
timestamp,
format,
...options.customMetadata,
};
let exportData = {
data,
metadata,
version: '1.0',
};
if (!options.encrypt) {
exportData.checksum = this.calculateChecksum(JSON.stringify(data));
}
let serializedData = this.serializeData(exportData, format, options.pretty);
if (options.encrypt) {
if (!options.encryptionKey && !((_a = this.options.encryption) === null || _a === void 0 ? void 0 : _a.secretKey)) {
throw new Error('Encryption key is required when encryption is enabled');
}
const encryptionKey = options.encryptionKey || ((_b = this.options.encryption) === null || _b === void 0 ? void 0 : _b.secretKey);
serializedData = this.encryptData(serializedData, encryptionKey);
}
const destination = options.destination || 'file';
let filePath;
let fileUrl;
switch (destination) {
case 'file':
filePath = await this.saveToFile(serializedData, options);
break;
case 'http':
if (!this.httpService) {
throw new Error('HttpService is required for HTTP destination but was not provided');
}
if (!options.url) {
throw new Error('URL is required for HTTP destination');
}
fileUrl = await this.sendToHttp(serializedData, options);
break;
case 'memory':
break;
default:
throw new Error(`Unsupported destination: ${destination}`);
}
return {
data,
metadata,
format,
size: serializedData.length,
path: filePath,
url: fileUrl,
success: true,
};
}
catch (error) {
this.logger.error(`Export failed: ${error.message}`, error.stack);
return {
data: null,
metadata: {
version: '1.0',
timestamp: new Date().toISOString(),
format: options.format || this.options.defaultFormat,
},
format: options.format || this.options.defaultFormat,
success: false,
errors: [error],
};
}
}
async importData(source, options = {}) {
var _a, _b, _c;
try {
const format = options.format || this.options.defaultFormat;
let rawData;
let importedData;
if (typeof source === 'string') {
if (options.source === 'http') {
if (!this.httpService) {
throw new Error('HttpService is required for HTTP source but was not provided');
}
rawData = await this.fetchFromHttp(source, options);
}
else if (options.source === 'file' || source.includes('/') || source.includes('\\')) {
rawData = await this.readFromFile(source);
}
else {
rawData = source;
}
}
else if (Buffer.isBuffer(source)) {
rawData = source;
}
else {
importedData = source;
}
if (!importedData) {
if (options.decrypt) {
if (!options.decryptionKey && !((_a = this.options.encryption) === null || _a === void 0 ? void 0 : _a.secretKey)) {
throw new Error('Decryption key is required when decryption is enabled');
}
const decryptionKey = options.decryptionKey || ((_b = this.options.encryption) === null || _b === void 0 ? void 0 : _b.secretKey);
rawData = this.decryptData(rawData, decryptionKey);
}
importedData = this.deserializeData(rawData, format);
}
if (options.validate || ((_c = this.options.validation) === null || _c === void 0 ? void 0 : _c.enabled)) {
this.validateImportData(importedData);
}
if (importedData.checksum) {
const calculatedChecksum = this.calculateChecksum(JSON.stringify(importedData.data));
if (calculatedChecksum !== importedData.checksum) {
throw new Error('Data integrity check failed: checksum mismatch');
}
}
let transformedData = importedData.data;
if (options.transformers && options.transformers.length > 0) {
transformedData = options.transformers.reduce((data, transformer) => transformer(data), transformedData);
}
return {
data: transformedData,
metadata: importedData.metadata,
format: importedData.metadata.format || format,
totalItems: Array.isArray(transformedData) ? transformedData.length : 1,
importedItems: Array.isArray(transformedData) ? transformedData.length : 1,
skippedItems: 0,
failedItems: 0,
success: true,
};
}
catch (error) {
this.logger.error(`Import failed: ${error.message}`, error.stack);
return {
data: null,
metadata: {
version: '1.0',
timestamp: new Date().toISOString(),
format: options.format || this.options.defaultFormat,
},
format: options.format || this.options.defaultFormat,
totalItems: 0,
importedItems: 0,
skippedItems: 0,
failedItems: 1,
success: false,
errors: [error],
};
}
}
createSchemaFromSample(sample, additionalProperties = true) {
const properties = {};
const required = [];
Object.entries(sample).forEach(([key, value]) => {
const type = typeof value;
properties[key] = { type };
if (value !== null && value !== undefined) {
required.push(key);
}
});
return {
type: 'object',
version: '1.0',
properties,
required,
additionalProperties,
};
}
validateImportData(data) {
if (!data) {
throw new Error('Import data is empty or invalid');
}
if (!data.data) {
throw new Error('Import data is missing the data property');
}
if (!data.metadata) {
throw new Error('Import data is missing metadata');
}
if (!data.version) {
throw new Error('Import data is missing version information');
}
}
calculateChecksum(data) {
return crypto.createHash('sha256').update(data).digest('hex');
}
serializeData(data, format = 'json', pretty = false) {
switch (format) {
case 'json':
return pretty ? JSON.stringify(data, null, 2) : JSON.stringify(data);
case 'csv':
if (!Array.isArray(data.data)) {
throw new Error('CSV format requires data to be an array');
}
const headers = Object.keys(data.data[0] || {});
let csv = headers.join(',') + '\n';
data.data.forEach(item => {
const row = headers.map(header => {
const value = item[header];
return typeof value === 'string' ? `"${value.replace(/"/g, '""')}"` : String(value);
});
csv += row.join(',') + '\n';
});
return csv;
case 'xml':
const json = JSON.stringify(data);
let xml = '<?xml version="1.0" encoding="UTF-8"?>\n<export>\n';
Object.entries(data).forEach(([key, value]) => {
if (key === 'data') {
xml += ` <data>\n`;
if (Array.isArray(value)) {
value.forEach((item, index) => {
xml += ` <item index="${index}">\n`;
Object.entries(item).forEach(([itemKey, itemValue]) => {
xml += ` <${itemKey}>${itemValue}</${itemKey}>\n`;
});
xml += ` </item>\n`;
});
}
else {
Object.entries(value).forEach(([dataKey, dataValue]) => {
xml += ` <${dataKey}>${dataValue}</${dataKey}>\n`;
});
}
xml += ` </data>\n`;
}
else {
xml += ` <${key}>${JSON.stringify(value)}</${key}>\n`;
}
});
xml += '</export>';
return xml;
default:
throw new Error(`Unsupported format: ${format}`);
}
}
deserializeData(data, format = 'json') {
const dataStr = data.toString();
switch (format) {
case 'json':
return JSON.parse(dataStr);
case 'csv':
const lines = dataStr.split('\n').filter(line => line.trim());
const headers = lines[0].split(',').map(h => h.trim());
const rows = [];
for (let i = 1; i < lines.length; i++) {
const values = this.parseCSVLine(lines[i]);
const row = {};
headers.forEach((header, index) => {
row[header] = values[index];
});
rows.push(row);
}
return {
data: rows,
metadata: {
version: '1.0',
timestamp: new Date().toISOString(),
format: 'csv',
},
version: '1.0',
};
case 'xml':
this.logger.warn('XML parsing not fully implemented, returning simplified structure');
return {
data: { message: 'XML import not fully implemented' },
metadata: {
version: '1.0',
timestamp: new Date().toISOString(),
format: 'xml',
},
version: '1.0',
};
default:
throw new Error(`Unsupported format: ${format}`);
}
}
parseCSVLine(line) {
const values = [];
let inQuotes = false;
let currentValue = '';
for (let i = 0; i < line.length; i++) {
const char = line[i];
if (char === '"') {
if (i + 1 < line.length && line[i + 1] === '"') {
currentValue += '"';
i++;
}
else {
inQuotes = !inQuotes;
}
}
else if (char === ',' && !inQuotes) {
values.push(currentValue);
currentValue = '';
}
else {
currentValue += char;
}
}
values.push(currentValue);
return values;
}
encryptData(data, secretKey) {
var _a;
const algorithm = ((_a = this.options.encryption) === null || _a === void 0 ? void 0 : _a.algorithm) || 'aes-256-cbc';
const iv = crypto.randomBytes(16);
const key = crypto.createHash('sha256').update(secretKey).digest('base64').substring(0, 32);
const cipher = crypto.createCipheriv(algorithm, key, iv);
let encrypted = cipher.update(data, 'utf8', 'hex');
encrypted += cipher.final('hex');
return iv.toString('hex') + ':' + encrypted;
}
decryptData(data, secretKey) {
var _a;
const algorithm = ((_a = this.options.encryption) === null || _a === void 0 ? void 0 : _a.algorithm) || 'aes-256-cbc';
const parts = data.split(':');
if (parts.length !== 2) {
throw new Error('Invalid encrypted data format');
}
const iv = Buffer.from(parts[0], 'hex');
const encrypted = parts[1];
const key = crypto.createHash('sha256').update(secretKey).digest('base64').substring(0, 32);
const decipher = crypto.createDecipheriv(algorithm, key, iv);
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
async saveToFile(data, options) {
const baseDir = options.path || this.options.basePath;
let filename = options.filename;
if (!filename) {
const timestamp = new Date().toISOString().replace(/:/g, '-');
const format = options.format || this.options.defaultFormat;
filename = `export_${timestamp}.${format}`;
}
const filePath = path.join(baseDir, filename);
const dirPath = path.dirname(filePath);
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
}
const writeFile = (0, util_1.promisify)(fs.writeFile);
await writeFile(filePath, data);
this.logger.verbose(`Data exported to file: ${filePath}`);
return filePath;
}
async readFromFile(filePath) {
if (!fs.existsSync(filePath)) {
throw new Error(`File not found: ${filePath}`);
}
const readFile = (0, util_1.promisify)(fs.readFile);
const data = await readFile(filePath, 'utf8');
this.logger.verbose(`Data imported from file: ${filePath}`);
return data;
}
async sendToHttp(data, options) {
if (!this.httpService) {
throw new Error('HttpService is required for HTTP operations');
}
if (!options.url) {
throw new Error('URL is required for HTTP operations');
}
const headers = {
'Content-Type': this.getContentTypeForFormat(options.format),
...options.headers,
};
const response = await (0, rxjs_1.firstValueFrom)(this.httpService.post(options.url, data, { headers }));
this.logger.verbose(`Data exported to URL: ${options.url}`);
return options.url;
}
async fetchFromHttp(url, options) {
if (!this.httpService) {
throw new Error('HttpService is required for HTTP operations');
}
const headers = options.headers || {};
const response = await (0, rxjs_1.firstValueFrom)(this.httpService.get(url, { headers }));
this.logger.verbose(`Data imported from URL: ${url}`);
return response.data;
}
getContentTypeForFormat(format) {
switch (format) {
case 'json':
return 'application/json';
case 'csv':
return 'text/csv';
case 'xml':
return 'application/xml';
default:
return 'application/octet-stream';
}
}
};
exports.DataTransferService = DataTransferService;
exports.DataTransferService = DataTransferService = DataTransferService_1 = __decorate([
(0, common_1.Injectable)(),
__param(0, (0, common_1.Optional)()),
__param(0, (0, common_1.Inject)('DATA_TRANSFER_OPTIONS')),
__param(1, (0, common_1.Optional)()),
__metadata("design:paramtypes", [Object, axios_1.HttpService])
], DataTransferService);
//# sourceMappingURL=data-transfer.service.js.map