UNPKG

@b2y/document-module

Version:

A flexible multi-provider storage adapter for file operations across S3, Azure Blob, Google Drive, and local storage

144 lines (122 loc) 4.51 kB
// src/providers/AmazonS3StorageProvider.js const { S3Client, PutObjectCommand, GetObjectCommand, DeleteObjectCommand } = require('@aws-sdk/client-s3'); const { getSignedUrl } = require('@aws-sdk/s3-request-presigner'); const fs = require('fs'); const path = require('path'); const stream = require('stream'); const BaseStorageProvider = require('./BaseStorageProvider'); const logger = require('../../logger'); class AmazonS3StorageProvider extends BaseStorageProvider { constructor(config = {}) { super(); // Allow passing configuration directly or using environment variables this.bucketName = config.bucketName || process.env.AWS_S3_BUCKET_NAME; if (!this.bucketName) { throw new Error("AWS S3 bucket name must be specified."); } this.client = new S3Client({ region: config.region || process.env.AWS_REGION, credentials: { accessKeyId: config.accessKeyId || process.env.AWS_ACCESS_KEY_ID, secretAccessKey: config.secretAccessKey || process.env.AWS_SECRET_ACCESS_KEY } }); // Default URL expiration time in seconds (24 hours) this.urlExpiresIn = config.urlExpiresIn || process.env.AWS_URL_EXPIRES_IN ? parseInt(config.urlExpiresIn || process.env.AWS_URL_EXPIRES_IN) : 86400; } async uploadFile(file, destinationPath) { try { let fileContent; // If file is a stream, use it directly if (file.stream) { fileContent = file.stream; } // 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); } // If file is a buffer else if (Buffer.isBuffer(file)) { fileContent = file; } // Construct the full S3 key with bucket name at the beginning const fullPath = `${this.bucketName}/${destinationPath}`; const params = { Bucket: this.bucketName, Key: destinationPath, Body: fileContent, ContentType: file.mimetype || 'application/octet-stream' }; const command = new PutObjectCommand(params); await this.client.send(command); // Return the path to be stored in the database return { success: true, path: fullPath, // No URL is returned as we'll generate it on demand }; } catch (error) { logger.error('Error uploading file to S3:', error); throw error; } } async getFile(filePath) { try { // Extract the actual key from the full path if needed let key = filePath; if (filePath.startsWith(`${this.bucketName}/`)) { key = filePath.substring(this.bucketName.length + 1); } const params = { Bucket: this.bucketName, Key: key }; const command = new GetObjectCommand(params); const response = await this.client.send(command); return response.Body; } catch (error) { logger.error('Error getting file from S3:', error); throw error; } } async deleteFile(filePath) { try { // Extract the actual key from the full path if needed let key = filePath; if (filePath.startsWith(`${this.bucketName}/`)) { key = filePath.substring(this.bucketName.length + 1); } const params = { Bucket: this.bucketName, Key: key }; const command = new DeleteObjectCommand(params); await this.client.send(command); return { success: true }; } catch (error) { console.error('Error deleting file from S3:', error); throw error; } } async getPublicUrl(filePath) { // Extract the actual key from the full path if needed let key = filePath; if (filePath.startsWith(`${this.bucketName}/`)) { key = filePath.substring(this.bucketName.length + 1); } // Generate a presigned URL that expires after the configured time const command = new GetObjectCommand({ Bucket: this.bucketName, Key: key }); // Return a promise that resolves to the signed URL return getSignedUrl(this.client, command, { expiresIn: this.urlExpiresIn }); } } module.exports = AmazonS3StorageProvider;