Some checks are pending
Backend CI / test (push) Waiting to run
Backend CI / build (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
165 lines
4.4 KiB
Dart
165 lines
4.4 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,
|
|
enableTrace: true,
|
|
);
|
|
}
|
|
|
|
/// Ensures all required buckets exist, creates them if they don't
|
|
Future<void> ensureBucketsExist() async {
|
|
final buckets = [
|
|
MinioConfig.cardImagesBucket,
|
|
MinioConfig.voiceAudioBucket,
|
|
];
|
|
print('Config endpoint: ${_config.endpoint}');
|
|
print('Config port: ${_config.port}');
|
|
print('Config accessKey: ${_config.accessKey}');
|
|
print('Config secretKey: ${_config.secretKey}');
|
|
print('Config useSSL: ${_config.useSSL}');
|
|
print('Config region: ${_config.region}');
|
|
|
|
|
|
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: 7 days)
|
|
///
|
|
/// Returns the presigned URL, or null if the object doesn't exist
|
|
Future<String?> getPresignedUrl({
|
|
required String bucket,
|
|
required String objectId,
|
|
int? expirySeconds,
|
|
}) async {
|
|
final expiry = expirySeconds ?? MinioConfig.presignedUrlExpirySeconds;
|
|
try {
|
|
// Check if object exists
|
|
await _client.statObject(bucket, objectId);
|
|
|
|
// Generate presigned URL
|
|
final url = await _client.presignedGetObject(
|
|
bucket,
|
|
objectId,
|
|
expires: expiry,
|
|
);
|
|
|
|
print('✅ Generated presigned URL for $bucket/$objectId (expires in ${expiry}s)');
|
|
return url;
|
|
} on MinioException catch (e) {
|
|
print(
|
|
'❌ MinIO error generating presigned URL for $bucket/$objectId: ${e.code} - ${e.message}',
|
|
);
|
|
return null;
|
|
} catch (e, s) {
|
|
print(
|
|
'❌ Unexpected error generating presigned URL for $bucket/$objectId: $e\n$s',
|
|
);
|
|
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;
|
|
}
|
|
}
|
|
}
|