UNPKG

bb-inspired

Version:

Core library for BB-inspired NestJS backend

452 lines 18.9 kB
"use strict"; 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