@3akram/strapi-provider-upload-google-cloud-storage
Version:
Google Cloud Storage provider for Strapi v5 upload
154 lines (131 loc) • 6.43 kB
text/typescript
import { Storage } from '@google-cloud/storage';
import type { ProviderOptions, File } from './types';
import { logger, validateConfig } from './utils';
export = {
init(config: ProviderOptions) {
// Validate the configuration and set log level if provided
validateConfig(config);
// Log configuration with appropriate level (this will only show if logLevel allows info logs)
logger.info('Initializing GCS provider with config:', {
...config,
// Don't log any potential credentials that might be in the config
_sanitized: true
});
// Initialize Google Cloud Storage
const storage = new Storage();
const bucket = storage.bucket(config.bucketName);
logger.info(`Connected to bucket: ${config.bucketName}`);
// Helper function to construct file path
const getFilePath = (filename: string): string => {
const basePath = config.basePath?.replace(/^\/+|\/+$/g, '') || '';
return basePath ? `${basePath}/${filename}` : filename;
};
// Helper function to create the public url
const getPublicUrl = (filepath: string) => {
const {bucketName, baseUrl} = config;
const base = baseUrl ?? `https://storage.googleapis.com/${bucketName}`;
return `${base}/${filepath}`;
}
return {
async upload(file: File) {
try {
logger.info('Starting upload for file:', {
name: file.name,
hash: file.hash,
ext: file.ext,
mime: file.mime,
size: file.size,
});
// Create a unique filename and path
const filename = `${file.hash}${file.ext}`;
const filepath = getFilePath(filename);
logger.info(`Generated filepath: ${filepath}`);
// Create a new blob in the bucket
const blob = bucket.file(filepath);
logger.info('Created blob reference');
// Prepare upload options
const uploadOptions: any = {
resumable: false,
metadata: {
contentType: file.mime,
},
};
// Add predefined ACL if uniform access is not enabled
if (!config.uniform && config.publicFiles) {
uploadOptions.predefinedAcl = 'publicRead';
}
// Upload the file data
const blobStream = blob.createWriteStream(uploadOptions);
logger.info('Created write stream with options:', uploadOptions);
// Handle errors during upload
return new Promise((resolve, reject) => {
blobStream.on('error', error => {
logger.error(`Error uploading file ${filepath}:`, error);
reject(error);
});
blobStream.on('finish', async () => {
try {
// Check if the file exists in the bucket
const [exists] = await blob.exists();
logger.info(`File exists in bucket: ${exists}`);
if (!exists) {
throw new Error('File was not uploaded successfully');
}
// Make the file public if needed and uniform access is not enabled
if (!config.uniform && config.publicFiles) {
await blob.makePublic();
logger.info('File made public');
}
// Construct the public URL
const publicUrl = getPublicUrl(filepath);
logger.info(`Upload finished. Public URL: ${publicUrl}`);
const result = {
...file,
url: publicUrl,
};
file.url = publicUrl;
logger.info('Returning result:', result);
resolve(result);
} catch (error) {
logger.error('Error in finish handler:', error);
reject(error);
}
});
logger.info('Writing file buffer to stream...');
if (!file.buffer) {
logger.error('File buffer is undefined or null');
reject(new Error('File buffer is missing'));
return;
}
blobStream.end(file.buffer);
});
} catch (error) {
logger.error('Error in upload:', error);
throw error;
}
},
async delete(file: File) {
try {
if (!file.url) {
return;
}
// Extract filename from the URL
const urlParts = file.url.split('/');
const filename = urlParts[urlParts.length - 1];
if (!filename) {
throw new Error('Could not extract filename from URL');
}
// Get the full filepath
const filepath = getFilePath(filename);
logger.info(`Deleting file: ${filepath}`);
// Delete the file
await bucket.file(filepath).delete();
logger.info('File deleted successfully');
} catch (error) {
logger.error('Error in delete:', error);
throw error;
}
},
};
},
};