import * as azure from 'azure-storage'; import * as fs from 'fs'; export interface IContainerConfig { name: string; isPublic: boolean; expiry?: number; } export default class FileStorage { private blob: any; private containers: IContainerConfig[]; constructor( storageAccountOrConnectionString: string, storageAccessKey: string, host: string | null, ) { this.blob = azure.createBlobService( storageAccountOrConnectionString, storageAccessKey, host, ); } /*============================================================================ PUBLIC METHODS ============================================================================*/ public createContainers = async (containers: IContainerConfig[]) => { this.containers = containers; const createContainer = (containerConfig) => { return new Promise((resolve, reject) => { this.blob.createContainerIfNotExists(containerConfig.name, { publicAccessLevel: containerConfig.isPublic ? 'blob' : '', }, (err: any) => { if (err) { reject(err); } else { resolve('done'); } }); }); }; const promises = containers.map(container => createContainer(container)); try { await Promise.all(promises); return {status: 'done'}; } catch (err) { return {status: 'done'}; } } public create = async ( containerName: string, filename: string, file: string, ) => { const container = this.getContainerByName(containerName); const blob = await this.doCreateBlob(container.name, filename, file); if (container.isPublic) { return this.getPublicUrl(container.name, filename); } else { return blob; } } public remove = async ( containerName: string, filename: string, ) => ( this.doRemoveBlob(containerName, filename) ) public replace = async ( containerName: string, filename: string, file: string, ) => { const container = this.getContainerByName(containerName); await this.doRemoveBlob(containerName, filename); const blob = await this.doCreateBlob(container.name, filename, file); if (container.isPublic) { return this.getPublicUrl(container.name, filename); } else { return blob; } } public getPublicUrl = async ( containerName: string, filename: string, ) => ( this.blob.getUrl(containerName, filename) ) public getPrivateUrl = async ( containerName: string, filename: string, ) => { if (!filename) { return null; } const container = this.getContainerByName(containerName); const startDate = new Date(); const expiryDate = new Date(startDate); startDate.setMilliseconds(startDate.getMilliseconds() - 10000); expiryDate.setMinutes(startDate.getMinutes() + container.expiry); const sharedAccessPolicy = { AccessPolicy: { Permissions: azure.BlobUtilities.SharedAccessPermissions.READ, Start: startDate, Expiry: expiryDate, }, }; const token = this.blob.generateSharedAccessSignature(containerName, filename, sharedAccessPolicy); return this.blob.getUrl(containerName, filename, token); } public generateSasToken( containerName: string, blobName: string, permissions: string, ) { const connString = process.env.AzureWebJobsStorage; const blobService = azure.createBlobService(connString); // Create a SAS token that expires in an hour // Set start time to five minutes ago to avoid clock skew. const startDate = new Date(); startDate.setMinutes(startDate.getMinutes() - 5); const expiryDate = new Date(startDate); expiryDate.setMinutes(startDate.getMinutes() + 60); permissions = permissions || azure.BlobUtilities.SharedAccessPermissions.WRITE; const sharedAccessPolicy = { AccessPolicy: { Permissions: permissions, Start: startDate, Expiry: expiryDate, }, }; const sasToken = blobService.generateSharedAccessSignature(containerName, blobName, sharedAccessPolicy); return { token: sasToken, uri: blobService.getUrl(containerName, blobName, sasToken, true), }; } /*============================================================================ PRIVATE METHODS ============================================================================*/ private doCreateBlob = async ( containerName: string, filename: string, file: string, ) => ( new Promise((resolve, reject) => { const stream = fs.createReadStream(file); const filesize = fs.statSync(file).size; this.blob.createBlockBlobFromStream(containerName, filename, stream, filesize, (err, result) => { if (err) { reject(err); } else { resolve(result); } }); }) ) private doRemoveBlob = async ( containerName: string, filename: string, ) => ( new Promise((resolve, reject) => { this.blob.deleteBlobIfExists(containerName, filename, (err, result) => { if (err) { reject(err); } else { resolve(result); } }); }) ) private getContainerByName = (name: string) => { let result: IContainerConfig | null = null; this.containers.forEach(container => { if (container.name === name) { result = container; } }); return result; } }