20dd43c093
Mirror to GitHub / mirror (push) Successful in 28s
Test and Lint / backend-test (push) Successful in 1m26s
continuous-integration/drone/push Build is failing
Test and Lint / frontend-test (push) Failing after 2m18s
Version and Release / version-bump (push) Successful in 42s
Version and Release / trigger-drone (push) Successful in 3s
- Add S3/MinIO storage adapter with multipart upload support - Implement database backup service for SQLite and PostgreSQL - Create backup manifest generator for tracking backup contents - Enhance backup service with S3 integration and incremental backups - Add restore service with safety measures and rollback capability - Create comprehensive test suite for all backup functionality - Add admin API endpoints for backup/restore management - Implement frontend UI with dashboard, configuration, and restore wizard - Add roadmap section to README with implemented backup feature This implementation provides: - Multiple backup destinations (local, rsync, S3/MinIO) - Intelligent change detection to minimize backup frequency - Full database backups with compression - Manifest-based restore with integrity validation - Pre-restore safety backups with rollback - Comprehensive error handling and monitoring - User-friendly admin interface 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
221 lines
6.5 KiB
JavaScript
221 lines
6.5 KiB
JavaScript
/**
|
|
* Example usage of the S3StorageAdapter
|
|
*
|
|
* This file demonstrates how to use the S3 storage adapter for various operations
|
|
*/
|
|
|
|
const S3StorageAdapter = require('./s3Storage');
|
|
|
|
// Example 1: Basic AWS S3 Configuration
|
|
const s3Storage = new S3StorageAdapter({
|
|
bucket: 'my-photo-bucket',
|
|
region: 'us-east-1',
|
|
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
|
|
});
|
|
|
|
// Example 2: MinIO Configuration (S3-compatible)
|
|
const minioStorage = new S3StorageAdapter({
|
|
bucket: 'photo-storage',
|
|
endpoint: 'http://localhost:9000', // MinIO endpoint
|
|
accessKeyId: 'minioadmin',
|
|
secretAccessKey: 'minioadmin',
|
|
forcePathStyle: true, // Required for MinIO
|
|
sslEnabled: false // For local development
|
|
});
|
|
|
|
// Example 3: DigitalOcean Spaces Configuration
|
|
const spacesStorage = new S3StorageAdapter({
|
|
bucket: 'my-space-name',
|
|
endpoint: 'https://nyc3.digitaloceanspaces.com',
|
|
region: 'nyc3',
|
|
accessKeyId: process.env.DO_SPACES_KEY,
|
|
secretAccessKey: process.env.DO_SPACES_SECRET
|
|
});
|
|
|
|
// Usage Examples
|
|
async function examples() {
|
|
try {
|
|
// Test connection
|
|
await s3Storage.testConnection();
|
|
console.log('Connection successful!');
|
|
|
|
// Upload a file with progress tracking
|
|
const uploadResult = await s3Storage.upload(
|
|
'/path/to/local/photo.jpg',
|
|
'events/wedding-2024/photo.jpg',
|
|
{
|
|
contentType: 'image/jpeg',
|
|
metadata: {
|
|
event: 'wedding-2024',
|
|
photographer: 'John Doe'
|
|
},
|
|
onProgress: (loaded, total) => {
|
|
const percentage = Math.round((loaded / total) * 100);
|
|
console.log(`Upload progress: ${percentage}%`);
|
|
}
|
|
}
|
|
);
|
|
console.log('Upload complete:', uploadResult.Location);
|
|
|
|
// Upload from stream
|
|
const readStream = fs.createReadStream('/path/to/large-video.mp4');
|
|
await s3Storage.uploadStream(readStream, 'events/wedding-2024/video.mp4', {
|
|
contentType: 'video/mp4',
|
|
onProgress: (loaded, total) => {
|
|
console.log(`Streamed ${loaded} of ${total} bytes`);
|
|
}
|
|
});
|
|
|
|
// Download a file
|
|
await s3Storage.download(
|
|
'events/wedding-2024/photo.jpg',
|
|
'/path/to/downloaded/photo.jpg',
|
|
{
|
|
onProgress: (loaded, total) => {
|
|
const percentage = Math.round((loaded / total) * 100);
|
|
console.log(`Download progress: ${percentage}%`);
|
|
}
|
|
}
|
|
);
|
|
|
|
// Get a download stream
|
|
const downloadStream = await s3Storage.downloadStream('events/wedding-2024/photo.jpg');
|
|
downloadStream.pipe(fs.createWriteStream('/path/to/output.jpg'));
|
|
|
|
// List files
|
|
const listing = await s3Storage.list('events/wedding-2024/');
|
|
console.log(`Found ${listing.Contents.length} files`);
|
|
listing.Contents.forEach(file => {
|
|
console.log(`- ${file.Key} (${file.Size} bytes)`);
|
|
});
|
|
|
|
// Generate pre-signed URL for temporary access
|
|
const downloadUrl = await s3Storage.getSignedUrl('getObject', 'events/wedding-2024/photo.jpg', {
|
|
expiresIn: 3600 // 1 hour
|
|
});
|
|
console.log('Pre-signed download URL:', downloadUrl);
|
|
|
|
// Generate pre-signed upload URL
|
|
const uploadUrl = await s3Storage.getSignedUrl('putObject', 'events/wedding-2024/new-photo.jpg', {
|
|
expiresIn: 1800, // 30 minutes
|
|
params: {
|
|
ContentType: 'image/jpeg'
|
|
}
|
|
});
|
|
console.log('Pre-signed upload URL:', uploadUrl);
|
|
|
|
// Check if file exists
|
|
const exists = await s3Storage.exists('events/wedding-2024/photo.jpg');
|
|
console.log('File exists:', exists);
|
|
|
|
// Get metadata
|
|
const metadata = await s3Storage.getMetadata('events/wedding-2024/photo.jpg');
|
|
console.log('File metadata:', metadata);
|
|
|
|
// Copy file
|
|
await s3Storage.copy(
|
|
'events/wedding-2024/photo.jpg',
|
|
'events/wedding-2024/photo-copy.jpg'
|
|
);
|
|
|
|
// Move file
|
|
await s3Storage.move(
|
|
'events/wedding-2024/photo-copy.jpg',
|
|
'events/wedding-2024/archived/photo.jpg'
|
|
);
|
|
|
|
// Delete file
|
|
await s3Storage.delete('events/wedding-2024/temp-photo.jpg');
|
|
|
|
// Delete multiple files
|
|
const deleteResult = await s3Storage.deleteMany([
|
|
'events/wedding-2024/temp1.jpg',
|
|
'events/wedding-2024/temp2.jpg',
|
|
'events/wedding-2024/temp3.jpg'
|
|
]);
|
|
console.log(`Deleted ${deleteResult.Deleted.length} files`);
|
|
|
|
// Get storage statistics
|
|
const stats = await s3Storage.getStats('events/');
|
|
console.log(`Total files: ${stats.totalCount}`);
|
|
console.log(`Total size: ${stats.totalSizeFormatted}`);
|
|
|
|
// Listen to events
|
|
s3Storage.on('uploadProgress', (data) => {
|
|
console.log(`Uploading ${data.key}: ${data.loaded}/${data.total}`);
|
|
});
|
|
|
|
s3Storage.on('uploadComplete', (data) => {
|
|
console.log(`Upload completed: ${data.key}`);
|
|
});
|
|
|
|
s3Storage.on('uploadError', (data) => {
|
|
console.error(`Upload failed for ${data.key}:`, data.error);
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Error:', error);
|
|
}
|
|
}
|
|
|
|
// Integration with existing photo upload workflow
|
|
async function integrateWithPhotoUpload(eventId, files) {
|
|
const storage = new S3StorageAdapter({
|
|
bucket: process.env.S3_BUCKET,
|
|
region: process.env.AWS_REGION,
|
|
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
|
|
});
|
|
|
|
const uploadedPhotos = [];
|
|
|
|
for (const file of files) {
|
|
try {
|
|
// Generate unique S3 key
|
|
const s3Key = storage.generateKey(file.originalname, `events/${eventId}`);
|
|
|
|
// Upload to S3
|
|
const result = await storage.upload(file.path, s3Key, {
|
|
contentType: file.mimetype,
|
|
metadata: {
|
|
eventId: eventId,
|
|
originalName: file.originalname,
|
|
uploadedAt: new Date().toISOString()
|
|
}
|
|
});
|
|
|
|
uploadedPhotos.push({
|
|
filename: s3Key,
|
|
originalName: file.originalname,
|
|
size: file.size,
|
|
mimeType: file.mimetype,
|
|
s3Location: result.Location,
|
|
s3Key: s3Key
|
|
});
|
|
|
|
// Clean up local temp file
|
|
await fs.promises.unlink(file.path);
|
|
|
|
} catch (error) {
|
|
console.error(`Failed to upload ${file.originalname}:`, error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
return uploadedPhotos;
|
|
}
|
|
|
|
// Environment variables needed:
|
|
// AWS_ACCESS_KEY_ID=your-access-key
|
|
// AWS_SECRET_ACCESS_KEY=your-secret-key
|
|
// AWS_REGION=us-east-1
|
|
// S3_BUCKET=your-bucket-name
|
|
|
|
// For MinIO:
|
|
// MINIO_ENDPOINT=http://localhost:9000
|
|
// MINIO_ACCESS_KEY=minioadmin
|
|
// MINIO_SECRET_KEY=minioadmin
|
|
// MINIO_BUCKET=photo-storage
|
|
|
|
module.exports = { examples, integrateWithPhotoUpload }; |