UNPKG

wip-backup-manager

Version:

This utility enables backup & restore of Kentico Kontent projects

180 lines (147 loc) 7.1 kB
import { AssetContracts } from '@kentico/kontent-management'; import * as fs from 'fs'; import { get } from 'https'; import JSZip = require('jszip'); import { IExportAllResult } from '../export'; import { IBinaryFile, IImportSource } from '../import'; import { IZipServiceConfig } from './zip.models'; export class ZipService { private readonly zipExtension: string = '.zip'; private readonly contentTypesName: string = 'contentTypes.json'; private readonly contentItemsName: string = 'contentItems.json'; private readonly taxonomiesName: string = 'taxonomies.json'; private readonly assetsName: string = 'assets.json'; private readonly languageVariantsName: string = 'languageVariants.json'; private readonly contentTypeSnippetsName: string = 'contentTypesSnippets.json'; private readonly metadataName: string = 'metadata.json'; private readonly languages: string = 'languages.json'; private readonly filesName: string = 'files'; private readonly assetFoldersName: string = 'assetFolders.json'; private readonly validationName: string = 'validation.json'; private readonly filenameWithExtension: string; constructor(private config: IZipServiceConfig) { this.filenameWithExtension = config.filename + this.zipExtension; } public async extractZipAsync(): Promise<IImportSource> { const filePath = `./${this.filenameWithExtension}`; if (this.config.enableLog) { console.log(`Reading file '${filePath}'`); } const file = await fs.promises.readFile(filePath); if (this.config.enableLog) { console.log(`Unzipping file`); } const unzippedFile = await JSZip.loadAsync(file); if (this.config.enableLog) { console.log(`Parsing zip contents`); } const assets = await this.readAndParseJsonFile(unzippedFile, this.assetsName); const result = { importData: { assets, contentTypes: await this.readAndParseJsonFile(unzippedFile, this.contentTypesName), languageVariants: await this.readAndParseJsonFile(unzippedFile, this.languageVariantsName), languages: await this.readAndParseJsonFile(unzippedFile, this.languages), contentItems: await this.readAndParseJsonFile(unzippedFile, this.contentItemsName), contentTypeSnippets: await this.readAndParseJsonFile(unzippedFile, this.contentTypeSnippetsName), taxonomies: await this.readAndParseJsonFile(unzippedFile, this.taxonomiesName), }, assetFolders: await this.readAndParseJsonFile(unzippedFile, this.assetFoldersName), binaryFiles: await this.extractBinaryFilesAsync(unzippedFile, assets), validation: await this.readAndParseJsonFile(unzippedFile, this.validationName), metadata: await this.readAndParseJsonFile(unzippedFile, this.metadataName), }; if (this.config.enableLog) { console.log(`Pasing zip completed`); } return result; } public async createZipAsync(exportData: IExportAllResult): Promise<void> { const zip = new JSZip(); if (this.config.enableLog) { console.log(`Parsing json`); } zip.file(this.contentTypesName, JSON.stringify(exportData.data.contentTypes)); zip.file(this.validationName, JSON.stringify(exportData.validation)); zip.file(this.contentItemsName, JSON.stringify(exportData.data.contentItems)); zip.file(this.taxonomiesName, JSON.stringify(exportData.data.taxonomies)); zip.file(this.assetsName, JSON.stringify(exportData.data.assets)); zip.file(this.languageVariantsName, JSON.stringify(exportData.data.languageVariants)); zip.file(this.metadataName, JSON.stringify(exportData.metadata)); zip.file(this.languages, JSON.stringify(exportData.data.languages)); zip.file(this.contentTypeSnippetsName, JSON.stringify(exportData.data.contentTypeSnippets)); zip.file(this.assetFoldersName, JSON.stringify(exportData.data.assetFolders)); const assetsFolder = zip.folder(this.filesName); if (this.config.enableLog) { console.log(`Adding assets to zip`); } for (const asset of exportData.data.assets) { const assetIdShortFolder = assetsFolder.folder(asset.id.substr(0, 3)); const assetIdFolder = assetIdShortFolder.folder(asset.id); const assetFilename = asset.file_name; assetIdFolder.file(assetFilename, this.getBinaryDataFromUrl(asset.url, this.config.enableLog), { binary: true }); } const filePath = './' + this.filenameWithExtension; if (this.config.enableLog) { console.log(`Generating zip file '${filePath}'`); } const content = await zip.generateAsync({ type: 'nodebuffer' }); console.log(`Writing file '${filePath}'`); await fs.promises.writeFile(filePath, content); console.log(`File saved`); } private async extractBinaryFilesAsync( zip: JSZip, assets: AssetContracts.IAssetModelContract[] ): Promise<IBinaryFile[]> { const binaryFiles: IBinaryFile[] = []; const files = zip.files; for (const asset of assets) { const assetFile = files[this.getFullAssetPath(asset.id, asset.file_name)]; const binaryData = await assetFile.async('nodebuffer'); binaryFiles.push({ asset, binaryData }); } return binaryFiles; } /** * Gets path to asset within zip folder. Uses tree format using asset ids such as: * "files/3b4/3b42f36c-2e67-4605-a8d3-fee2498e5224/image.jpg" */ private getFullAssetPath(assetId: string, filename: string): string { return `${this.filesName}/${assetId.substr(0, 3)}/${assetId}/${filename}`; } private async readAndParseJsonFile(fileContents: any, filename: string): Promise<any> { const files = fileContents.files; const file = files[filename]; if (!file) { throw Error(`Invalid file '${filename}'`); } const text = await file.async('text'); return JSON.parse(text); } private getBinaryDataFromUrl(url: string, enableLog: boolean): Promise<any> { return new Promise((resolve, reject) => { get(url, res => { if (enableLog) { console.log(`Downloading asset: ${url}`); } const data: any[] = []; res.on('data', chunk => { data.push(chunk); }) .on('end', () => { const buffer = Buffer.concat(data); resolve(buffer); }) .on('error', error => { reject(error); }); }); }); } }