@b2y/document-module
Version:
A flexible multi-provider storage adapter for file operations across S3, Azure Blob, Google Drive, and local storage
208 lines (176 loc) • 7.86 kB
JavaScript
// src/providers/AzureBlobStorageProvider.js
const { BlobServiceClient, StorageSharedKeyCredential, generateBlobSASQueryParameters, BlobSASPermissions } = require('@azure/storage-blob');
const fs = require('fs');
const BaseStorageProvider = require('./BaseStorageProvider');
const logger = require('../../logger');
class AzureBlobStorageProvider extends BaseStorageProvider {
constructor(config = {}) {
super();
// Get Azure credentials from config or environment variables
const accountName = config.accountName || process.env.AZURE_STORAGE_ACCOUNT_NAME;
const accountKey = config.accountKey || process.env.AZURE_STORAGE_ACCOUNT_KEY;
const connectionString = config.connectionString || process.env.AZURE_STORAGE_CONNECTION_STRING;
// Initialize client either with connection string or with credentials
if (connectionString) {
this.blobServiceClient = BlobServiceClient.fromConnectionString(connectionString);
} else if (accountName && accountKey) {
const sharedKeyCredential = new StorageSharedKeyCredential(accountName, accountKey);
const blobServiceUrl = `https://${accountName}.blob.core.windows.net`;
this.blobServiceClient = new BlobServiceClient(blobServiceUrl, sharedKeyCredential);
this.sharedKeyCredential = sharedKeyCredential; // Store for SAS generation
} else {
throw new Error('Azure Storage credentials not provided. Set AZURE_STORAGE_CONNECTION_STRING or both AZURE_STORAGE_ACCOUNT_NAME and AZURE_STORAGE_ACCOUNT_KEY');
}
this.containerName = config.containerName || process.env.AZURE_STORAGE_CONTAINER_NAME;
this.containerClient = this.blobServiceClient.getContainerClient(this.containerName);
this.accountName = accountName || this.extractAccountNameFromConnectionString(connectionString);
// SAS token expiry duration in seconds (default 24 hours)
this.sasExpiryTime = config.sasExpiryTime || process.env.AZURE_SAS_EXPIRY_SECONDS
? parseInt(config.sasExpiryTime || process.env.AZURE_SAS_EXPIRY_SECONDS)
: 86400;
this.publicAccess = config.publicAccess || process.env.AZURE_PUBLIC_ACCESS === 'true';
}
// Helper to extract account name from connection string if needed
extractAccountNameFromConnectionString(connectionString) {
if (!connectionString) return null;
const accountNameMatch = connectionString.match(/AccountName=([^;]+)/i);
return accountNameMatch ? accountNameMatch[1] : null;
}
async ensureContainerExists() {
try {
const containerExists = await this.containerClient.exists();
if (!containerExists) {
logger.info(`Creating container ${this.containerName}`);
await this.containerClient.create({ access: 'blob' });
}
} catch (error) {
logger.error(`Error ensuring container exists: ${error.message}`);
throw error;
}
}
async uploadFile(file, destinationPath) {
try {
// Ensure container exists
await this.ensureContainerExists();
// Get a block blob client
const blockBlobClient = this.containerClient.getBlockBlobClient(destinationPath);
let fileContent;
let contentType = 'application/octet-stream';
// If file is a stream
if (file.stream) {
fileContent = file.stream;
contentType = file.mimetype || contentType;
}
// If file is a path, read from fs
else if (typeof file === 'string') {
fileContent = fs.createReadStream(file);
}
// If file is from multer
else if (file.path) {
fileContent = fs.createReadStream(file.path);
contentType = file.mimetype || contentType;
}
// If file is a buffer
else if (Buffer.isBuffer(file)) {
fileContent = file;
}
// Upload the file
const uploadOptions = {
blobHTTPHeaders: {
blobContentType: contentType
}
};
// If it's a stream, use uploadStream
if (fileContent.pipe && typeof fileContent.pipe === 'function') {
await blockBlobClient.uploadStream(fileContent, undefined, undefined, uploadOptions);
} else {
// Otherwise use uploadData
await blockBlobClient.uploadData(fileContent, uploadOptions);
}
// Store the complete path in the format containerName/blobName
const storedPath = `${this.containerName}/${destinationPath}`;
return {
success: true,
path: storedPath
};
} catch (error) {
logger.error('Error uploading file to Azure Blob Storage:', error);
throw error;
}
}
async getFile(filePath) {
try {
// Extract container and blob path if stored in combined format
let blobPath = filePath;
if (filePath.startsWith(`${this.containerName}/`)) {
blobPath = filePath.substring(this.containerName.length + 1);
}
const blockBlobClient = this.containerClient.getBlockBlobClient(blobPath);
const downloadResponse = await blockBlobClient.download(0);
return downloadResponse.readableStreamBody;
} catch (error) {
logger.error('Error getting file from Azure Blob Storage:', error);
throw error;
}
}
async deleteFile(filePath) {
try {
// Extract container and blob path if stored in combined format
let blobPath = filePath;
if (filePath.startsWith(`${this.containerName}/`)) {
blobPath = filePath.substring(this.containerName.length + 1);
}
const blockBlobClient = this.containerClient.getBlockBlobClient(blobPath);
await blockBlobClient.delete();
return { success: true };
} catch (error) {
logger.error('Error deleting file from Azure Blob Storage:', error);
throw error;
}
}
async getPublicUrl(filePath) {
try {
// Extract container and blob path if stored in combined format
let blobPath = filePath;
if (filePath.startsWith(`${this.containerName}/`)) {
blobPath = filePath.substring(this.containerName.length + 1);
}
const blockBlobClient = this.containerClient.getBlockBlobClient(blobPath);
// If container is configured for public access
if (this.publicAccess) {
return blockBlobClient.url;
}
// Otherwise generate a SAS token
if (!this.sharedKeyCredential && !process.env.AZURE_STORAGE_ACCOUNT_KEY) {
throw new Error('Shared key credential required for SAS token generation');
}
// Create shared key credential if not already created
if (!this.sharedKeyCredential) {
this.sharedKeyCredential = new StorageSharedKeyCredential(
this.accountName,
process.env.AZURE_STORAGE_ACCOUNT_KEY
);
}
// Set expiry time
const expiryTime = new Date();
expiryTime.setSeconds(expiryTime.getSeconds() + this.sasExpiryTime);
// Set permissions
const permissions = new BlobSASPermissions();
permissions.read = true;
// Generate SAS token
const sasToken = generateBlobSASQueryParameters({
containerName: this.containerName,
blobName: blobPath,
permissions: permissions,
startsOn: new Date(),
expiresOn: expiryTime,
}, this.sharedKeyCredential).toString();
// Return full URL with SAS token
return `${blockBlobClient.url}?${sasToken}`;
} catch (error) {
logger.error('Error generating public URL for Azure Blob Storage:', error);
throw error;
}
}
}
module.exports = AzureBlobStorageProvider;