const fs = require('fs'); const path = require('path'); const axios = require('axios'); const fetch = require('node-fetch'); const { logger } = require('~/config'); const { getAzureContainerClient } = require('./initialize'); const defaultBasePath = 'images'; /** * Uploads a buffer to Azure Blob Storage. * * Files will be stored at the path: {basePath}/{userId}/{fileName} within the container. * * @param {Object} params * @param {string} params.userId - The user's id. * @param {Buffer} params.buffer - The buffer to upload. * @param {string} params.fileName - The name of the file. * @param {string} [params.basePath='images'] - The base folder within the container. * @param {string} [params.containerName] - The Azure Blob container name. * @returns {Promise} The URL of the uploaded blob. */ async function saveBufferToAzure({ userId, buffer, fileName, basePath = defaultBasePath, containerName, }) { try { const containerClient = getAzureContainerClient(containerName); // Create the container if it doesn't exist. This is done per operation. await containerClient.createIfNotExists({ access: process.env.AZURE_STORAGE_PUBLIC_ACCESS ? 'blob' : undefined, }); const blobPath = `${basePath}/${userId}/${fileName}`; const blockBlobClient = containerClient.getBlockBlobClient(blobPath); await blockBlobClient.uploadData(buffer); return blockBlobClient.url; } catch (error) { logger.error('[saveBufferToAzure] Error uploading buffer:', error); throw error; } } /** * Saves a file from a URL to Azure Blob Storage. * * @param {Object} params * @param {string} params.userId - The user's id. * @param {string} params.URL - The URL of the file. * @param {string} params.fileName - The name of the file. * @param {string} [params.basePath='images'] - The base folder within the container. * @param {string} [params.containerName] - The Azure Blob container name. * @returns {Promise} The URL of the uploaded blob. */ async function saveURLToAzure({ userId, URL, fileName, basePath = defaultBasePath, containerName, }) { try { const response = await fetch(URL); const buffer = await response.buffer(); return await saveBufferToAzure({ userId, buffer, fileName, basePath, containerName }); } catch (error) { logger.error('[saveURLToAzure] Error uploading file from URL:', error); throw error; } } /** * Retrieves a blob URL from Azure Blob Storage. * * @param {Object} params * @param {string} params.fileName - The file name. * @param {string} [params.basePath='images'] - The base folder used during upload. * @param {string} [params.userId] - If files are stored in a user-specific directory. * @param {string} [params.containerName] - The Azure Blob container name. * @returns {Promise} The blob's URL. */ async function getAzureURL({ fileName, basePath = defaultBasePath, userId, containerName }) { try { const containerClient = getAzureContainerClient(containerName); const blobPath = userId ? `${basePath}/${userId}/${fileName}` : `${basePath}/${fileName}`; const blockBlobClient = containerClient.getBlockBlobClient(blobPath); return blockBlobClient.url; } catch (error) { logger.error('[getAzureURL] Error retrieving blob URL:', error); throw error; } } /** * Deletes a blob from Azure Blob Storage. * * @param {Object} params * @param {string} params.fileName - The name of the file. * @param {string} [params.basePath='images'] - The base folder where the file is stored. * @param {string} params.userId - The user's id. * @param {string} [params.containerName] - The Azure Blob container name. */ async function deleteFileFromAzure({ fileName, basePath = defaultBasePath, userId, containerName, }) { try { const containerClient = getAzureContainerClient(containerName); const blobPath = `${basePath}/${userId}/${fileName}`; const blockBlobClient = containerClient.getBlockBlobClient(blobPath); await blockBlobClient.delete(); logger.debug('[deleteFileFromAzure] Blob deleted successfully from Azure Blob Storage'); } catch (error) { logger.error('[deleteFileFromAzure] Error deleting blob:', error.message); if (error.statusCode === 404) { return; } throw error; } } /** * Uploads a file from the local file system to Azure Blob Storage. * * This function reads the file from disk and then uploads it to Azure Blob Storage * at the path: {basePath}/{userId}/{fileName}. * * @param {Object} params * @param {object} params.req - The Express request object. * @param {Express.Multer.File} params.file - The file object. * @param {string} params.file_id - The file id. * @param {string} [params.basePath='images'] - The base folder within the container. * @param {string} [params.containerName] - The Azure Blob container name. * @returns {Promise<{ filepath: string, bytes: number }>} An object containing the blob URL and its byte size. */ async function uploadFileToAzure({ req, file, file_id, basePath = defaultBasePath, containerName, }) { try { const inputFilePath = file.path; const inputBuffer = await fs.promises.readFile(inputFilePath); const bytes = Buffer.byteLength(inputBuffer); const userId = req.user.id; const fileName = `${file_id}__${path.basename(inputFilePath)}`; const fileURL = await saveBufferToAzure({ userId, buffer: inputBuffer, fileName, basePath, containerName, }); await fs.promises.unlink(inputFilePath); return { filepath: fileURL, bytes }; } catch (error) { logger.error('[uploadFileToAzure] Error uploading file:', error); throw error; } } /** * Retrieves a readable stream for a blob from Azure Blob Storage. * * @param {object} _req - The Express request object. * @param {string} fileURL - The URL of the blob. * @returns {Promise} A readable stream of the blob. */ async function getAzureFileStream(_req, fileURL) { try { const response = await axios({ method: 'get', url: fileURL, responseType: 'stream', }); return response.data; } catch (error) { logger.error('[getAzureFileStream] Error getting blob stream:', error); throw error; } } module.exports = { saveBufferToAzure, saveURLToAzure, getAzureURL, deleteFileFromAzure, uploadFileToAzure, getAzureFileStream, };