//TODO: split this into PresentationService, ScaffoldingService and abstract ExportService with derived ClmExport and AppExport services import * as path from 'path' import * as fs from 'fs-extra-promise' import * as Promise from 'bluebird' import { CoError } from '../../modules/co-error' import { template } from '@cobalt-engine/utils' import { PresentationRoutes, ExportOptions, ExportPostParams, ExportGetParams, ExportGetOptions, ExportData, ExportGetData, ClmBuildData, AppBuildData, DocBuildData, DocumentExtension, CobaltVersionData, PresentationDetail, PresentationPostParams, PresentationPostDiffParams, PresentationStatus, Status } from './interfaces' import { routes as routes} from './routes' import IRequest from '../../modules/interfaces/IRequest'; export * from './interfaces' const defaultMaxListSize = 10; export class PresentationClient{ private routes: PresentationRoutes private url: string private request: IRequest private documentExtension: DocumentExtension = { pdf: '.pdf', 'pdf-with-comments': '.pdf', ppt: '.ppt' } constructor(url: string, request: IRequest){ this.routes = routes this.url = url this.request = request; } public export(presentationId: string, type: string, subtype: string, params: ExportOptions): Promise{ const url: string = this.url + template(this.routes.exportById, { presentationId }) const data: ExportPostParams = { type: type, sub_type: subtype, options: JSON.stringify(params) } return this.request.post(url, { form: data }) } public getExportsList(options: ExportGetOptions = { top: defaultMaxListSize }): Promise { const url: string = this.url + this.routes.export const query : ExportGetParams = { type: options.type, sub_type: options.subtype, top: options.top, skip: options.skip } return this.request.get(url, { qs: query }) .get('list') .then(validateIsNotEmpty); } public getLastExports(options: ExportGetOptions = { top: defaultMaxListSize }): Promise<{}[]> { const url: string = this.url + this.routes.export return this.getExportsList(options) .then(validateIsNotEmpty) } public downloadExport(exportId: string, destDir: string, filename?: string): Promise{ return this.getExportInfo(exportId) .then(validateStatus) .then(exportInfo => { const type = exportInfo.type.code switch (type){ case 'clm': { return this.downloadClmBuild(exportId, destDir, filename) } case 'app':{ return this.downloadApp(exportId, destDir, filename) } case 'document':{ return this.downloadDocument(exportId, destDir, filename) } default: throw new CoError('UNKNOWN_EXPORT_TYPE', type) } }) } public downloadClmBuild(exportId: string, destDir: string, filename?: string): Promise { return this.getClmBuildInfo(exportId) .then((clmBuildInfo) => { const url: string = this.url + template(this.routes.download, { exportId }) const filePath = path.join(destDir, (filename || clmBuildInfo.sub_type.code) + '.zip') console.log('url', url) return this.request.download(url, filePath); }) } public downloadApp(exportId: string, destDir: string, filename?: string): Promise { return this.getAppInfo(exportId) .then((appInfo: AppBuildData )=> { const url: string = appInfo.last_build.application[appInfo.sub_type.code].download const filePath = path.join(destDir, filename || path.basename(url)) return this.request.download(url, filePath) }) } public downloadDocument(exportId: string, destDir: string, filename?: string): Promise { return this.getDocInfo(exportId) .then(docInfo => { const subType: string = docInfo.sub_type.code const url: string = this.url + template(this.routes.download, { exportId }) const filePath = path.join(destDir, (filename || subType) + this.documentExtension[subType]) return this.request.download(url, filePath) }) } public getExportInfo(exportId: string): Promise{ const url: string = this.url + template(this.routes.exportInfo, { exportId }) return this.request.get(url) } public getClmBuildInfo(exportId: string): Promise{ return this.getExportInfo(exportId) } public getAppInfo(exportId: string): Promise{ return this.getExportInfo(exportId) } public getDocInfo(exportId: string): Promise{ return this.getExportInfo(exportId) } public getVersionInfo(): Promise{ const url: string = this.url + this.routes.versionInfo return this.request.get(url) } public getScaffoldingVersion(cobaltVersion: string): Promise{ const url: string = this.url + this.routes.scaffoldingVersion const query = { cov: cobaltVersion } return this.request.get(url, { qs: query }) .get('scaffoldingVersion') } public getAllPresentations(options: ExportGetData = { skip: 0, top: defaultMaxListSize }): Promise{ const url: string = this.url + this.routes.presentation return this.request.get(url, { qs: options }) .get('list') .then(validateIsNotEmpty) } public getPresentationById(id: string): Promise{ const url: string = this.url + template(this.routes.presentationById, { id }) return this.request.get(url) .catch(handlePresentationError(id)) } public doesPresentationExist(id: string): Promise{ const url: string = this.url + template(this.routes.presentationById, { id }) return this.request.get(url) .then(() => true) .catch((err) => { if (err.code === 'SERVICE_NOT_FOUND') { return false; } throw err; }) } public getPresentationStatus(id: string): Promise{ return this.getPresentationById(id) .get('status') .get('type') .then(stringToStatus) } public getPresentationLatestVersion(id: string): Promise{ return this.getPresentationById(id) .get('version') } public createPresentation(zipPath: string): Promise{ const url: string = this.url + this.routes.presentation const data: PresentationPostParams = { package: fs.createReadStream(zipPath) } return this.request.post(url, { formData: data }) } public updatePresentation(zipPath: string, id: string): Promise{ const url: string = this.url + template(this.routes.presentationById, { id }) const data: PresentationPostParams = { package: fs.createReadStream(zipPath) } return this.request.put(url, { formData: data }) } public updatePresentationWithDiff(zipPath: string, removedFiles: string[], id: string): Promise{ const url: string = this.url + template(this.routes.presentationById, { id }) const data: PresentationPostDiffParams = { info: JSON.stringify({ deleted: removedFiles }), package: fs.createReadStream(zipPath) } return this.request.patch(url, { formData: data }); } /** * Tests eWizard instance if it has 'Push Diff' functionality. * * It sends PATCH request to presentation service with incorrect data and checks error code: * - 'BAD_REQUEST' means that functionality exists but we sent incorrect data(expected behavior); * - 'SERVICE_NOT_FOUND' means that 'Push Diff' is not supported. */ public isUpdateWithDiffSupported(id: string): Promise { const url: string = this.url + template(this.routes.presentationById, { id }) return this.request.patch(url, { formData: undefined }) .then(() => { throw new CoError('PUSH_PRESENTATION_CHECK_UNEXPECTED_RESULT'); }) .catch((error: CoError) => { if (error.code === 'BAD_REQUEST') { return true; } else if (error.code === 'SERVICE_NOT_FOUND') { return false; } // Unexpected error. throw error; }); } public downloadPresentation(id: string, filename: string): Promise{ return this.getPresentationById(id) .get('links') .then(links => this.request.download(links.download, filename)) } public deletePresentation(id: string): Promise { const url: string = this.url + template(this.routes.presentationById, { id }) return this.request.delete(url) } } function handlePresentationError (presentationId: string): (err: CoError) => never { return function (err) { if(err.code === 'SERVICE_NOT_FOUND'){ throw new CoError('PRESENTATION_NOT_EXIST', presentationId) } throw err } } function validateIsNotEmpty(list: T[]): T[]{ if(!list.length){ throw new CoError('EXPORT_EMPTY_LIST') } return list } function validateStatus(exportInfo: ExportData): ExportData{ const currentStatus = stringToStatus(exportInfo.last_build.status[exportInfo.last_build.status.length - 1].type) if(currentStatus === Status.Failed){ throw new CoError('EXPORT_FAILED', exportInfo.type.name, exportInfo.sub_type.code, exportInfo.id) }else if(currentStatus === Status.InProgress){ throw new CoError('EXPORT_IN_PROGRESS', exportInfo.type.name, exportInfo.sub_type.code, exportInfo.id) } return exportInfo } function stringToStatus(status: string): Status{ switch (status){ case 'success': return Status.Success case 'inprogress': return Status.InProgress case 'failed': return Status.Failed default: return Status.Failed } }