mnemo_cards/mnemo_cards_backend/lib/storage/minio_service.dart
Dmitry 7aa12d0c59
Some checks are pending
Backend CI / test (push) Waiting to run
Backend CI / build (push) Blocked by required conditions
Mobile App CI / test (push) Waiting to run
Mobile App CI / build-android (push) Blocked by required conditions
Mobile App CI / build-ios (push) Blocked by required conditions
Web App CI / test (push) Waiting to run
Web App CI / build (push) Blocked by required conditions
Deploy Admin Panel / Deploy Admin Panel (push) Waiting to run
Deploy Admin Panel / Admin Panel Verification (push) Blocked by required conditions
Deploy Mnemo Cards / Deploy Backend (push) Waiting to run
Deploy Mnemo Cards / Deploy Web App (push) Blocked by required conditions
Deploy Mnemo Cards / Final Verification (push) Blocked by required conditions
Deploy Telegram Bot / Deploy Telegram Bot (push) Waiting to run
minio
2025-12-19 03:56:58 +03:00

151 lines
3.9 KiB
Dart

import 'dart:typed_data';
import 'package:injectable/injectable.dart';
import 'package:minio/minio.dart';
import 'package:uuid/uuid.dart';
import 'minio_config.dart';
/// Service for interacting with MinIO object storage
@lazySingleton
class MinioService {
final MinioConfig _config;
late final Minio _client;
final _uuid = const Uuid();
MinioService(this._config) {
_client = Minio(
endPoint: _config.endpoint,
port: _config.port,
accessKey: _config.accessKey,
secretKey: _config.secretKey,
useSSL: _config.useSSL,
region: _config.region,
);
}
/// Ensures all required buckets exist, creates them if they don't
Future<void> ensureBucketsExist() async {
final buckets = [
MinioConfig.cardImagesBucket,
MinioConfig.testImagesBucket,
MinioConfig.voiceAudioBucket,
];
for (final bucket in buckets) {
try {
final exists = await _client.bucketExists(bucket);
if (!exists) {
await _client.makeBucket(bucket);
print('✅ Created MinIO bucket: $bucket');
} else {
print('✅ MinIO bucket exists: $bucket');
}
} catch (e) {
print('❌ Error checking/creating bucket $bucket: $e');
rethrow;
}
}
}
/// Uploads a file to MinIO and returns the object ID
///
/// [bucket] - The bucket name
/// [bytes] - File content as bytes
/// [contentType] - MIME type of the file
/// [objectId] - Optional object ID (UUID). If not provided, generates a new one
///
/// Returns the object ID (UUID) that can be stored in the database
Future<String> uploadFile({
required String bucket,
required Uint8List bytes,
required String contentType,
String? objectId,
}) async {
final id = objectId ?? _uuid.v4();
try {
await _client.putObject(
bucket,
id,
Stream.value(bytes),
size: bytes.length,
metadata: {
'Content-Type': contentType,
},
);
print('✅ Uploaded file to MinIO: $bucket/$id (${bytes.length} bytes)');
return id;
} catch (e) {
print('❌ Error uploading file to MinIO: $e');
rethrow;
}
}
/// Generates a presigned URL for getting a file from MinIO
///
/// [bucket] - The bucket name
/// [objectId] - The object ID (UUID)
/// [expirySeconds] - Optional expiry time in seconds (default: 4 hours)
///
/// Returns the presigned URL, or null if the object doesn't exist
Future<String?> getPresignedUrl({
required String bucket,
required String objectId,
int? expirySeconds,
}) async {
try {
// Check if object exists
await _client.statObject(bucket, objectId);
// Generate presigned URL
final url = await _client.presignedGetObject(
bucket,
objectId,
expires: expirySeconds ?? MinioConfig.presignedUrlExpirySeconds,
);
return url;
} catch (e) {
print(
'⚠️ Warning: Failed to generate presigned URL for $bucket/$objectId: $e',
);
return null;
}
}
/// Deletes a file from MinIO
///
/// [bucket] - The bucket name
/// [objectId] - The object ID (UUID) to delete
Future<void> deleteFile({
required String bucket,
required String objectId,
}) async {
try {
await _client.removeObject(bucket, objectId);
print('✅ Deleted file from MinIO: $bucket/$objectId');
} catch (e) {
print('❌ Error deleting file from MinIO: $e');
rethrow;
}
}
/// Checks if a file exists in MinIO
///
/// [bucket] - The bucket name
/// [objectId] - The object ID (UUID) to check
///
/// Returns true if the file exists, false otherwise
Future<bool> fileExists({
required String bucket,
required String objectId,
}) async {
try {
await _client.statObject(bucket, objectId);
return true;
} catch (e) {
return false;
}
}
}