200 lines
6.3 KiB
Dart
200 lines
6.3 KiB
Dart
import 'dart:typed_data';
|
|
import 'package:file_picker/file_picker.dart';
|
|
import 'package:flux/features/attachments/models/attachment_model.dart';
|
|
import 'package:supabase_flutter/supabase_flutter.dart';
|
|
import 'package:flux/features/attachments/blocs/attachments_bloc.dart';
|
|
|
|
class AttachmentsRepository {
|
|
final _supabase = Supabase.instance.client;
|
|
static const String _bucketName = 'documents';
|
|
static const String _tableName =
|
|
'attachment'; // Cambia col vero nome della tua tabella se diverso!
|
|
|
|
/// Scarica i byte di un file direttamente da Supabase Storage
|
|
Future<Uint8List> downloadAttachmentBytes(String storagePath) async {
|
|
try {
|
|
final Uint8List bytes = await _supabase.storage
|
|
.from(_bucketName)
|
|
.download(storagePath);
|
|
return bytes;
|
|
} catch (e) {
|
|
throw Exception("Impossibile scaricare il documento dal cloud: $e");
|
|
}
|
|
}
|
|
|
|
/// RESTITUISCE IL NOME DELLA COLONNA DB IN BASE AL TIPO
|
|
String _getColumnNameForParent(AttachmentParentType parentType) {
|
|
switch (parentType) {
|
|
case AttachmentParentType.operation:
|
|
return 'operation_id';
|
|
case AttachmentParentType.ticket:
|
|
return 'ticket_id';
|
|
case AttachmentParentType.customer:
|
|
return 'customer_id';
|
|
}
|
|
}
|
|
|
|
/// RECUPERA I FILE IN TEMPO REALE
|
|
Stream<List<AttachmentModel>> getFilesStream(
|
|
String parentId,
|
|
AttachmentParentType parentType,
|
|
) {
|
|
final columnName = _getColumnNameForParent(parentType);
|
|
|
|
return _supabase
|
|
.from(_tableName)
|
|
.stream(primaryKey: ['id'])
|
|
.eq(columnName, parentId)
|
|
.map(
|
|
(listOfMaps) =>
|
|
listOfMaps.map((map) => AttachmentModel.fromMap(map)).toList(),
|
|
);
|
|
}
|
|
|
|
/// CARICA IL FILE NELLO STORAGE E LO REGISTRA NEL DB
|
|
Future<void> uploadAndRegisterFile({
|
|
required String parentId,
|
|
required AttachmentParentType parentType,
|
|
required PlatformFile pickedFile,
|
|
required String companyId,
|
|
}) async {
|
|
try {
|
|
if (pickedFile.bytes == null) {
|
|
throw Exception(
|
|
"I bytes del file sono vuoti! Ricarica la pagina senza cache.",
|
|
);
|
|
}
|
|
|
|
final extension = pickedFile.extension ?? pickedFile.name.split('.').last;
|
|
final cleanName = pickedFile.name
|
|
.replaceAll(RegExp(r'[^\w\s\.-]'), '')
|
|
.replaceAll(' ', '_');
|
|
|
|
// Creiamo un path ordinato: idAzienda/tipoEntita/idEntita/timestamp_nomefile
|
|
final timestamp = DateTime.now().millisecondsSinceEpoch;
|
|
final storagePath =
|
|
'$companyId/${parentType.name}/$parentId/${timestamp}_$cleanName';
|
|
|
|
// 1. Upload su Supabase Storage
|
|
await _supabase.storage
|
|
.from(_bucketName)
|
|
.uploadBinary(
|
|
storagePath,
|
|
pickedFile.bytes!,
|
|
fileOptions: FileOptions(contentType: _guessContentType(extension)),
|
|
);
|
|
|
|
// 2. Creiamo la mappa per il DB dinamicamente
|
|
final Map<String, dynamic> insertData = {
|
|
'company_id': companyId,
|
|
'name': pickedFile.name.replaceAll('.$extension', ''),
|
|
'extension': extension,
|
|
'file_size': pickedFile.size,
|
|
'storage_path': storagePath,
|
|
};
|
|
|
|
// Inseriamo l'ID nella colonna giusta!
|
|
final columnName = _getColumnNameForParent(parentType);
|
|
insertData[columnName] = parentId;
|
|
|
|
// 3. Salviamo su Postgres
|
|
await _supabase.from(_tableName).insert(insertData);
|
|
} catch (e) {
|
|
throw Exception("Errore caricamento: $e");
|
|
}
|
|
}
|
|
|
|
/// ELIMINA IL FILE (Scollegamento intelligente)
|
|
Future<void> deleteFiles({
|
|
required List<AttachmentModel> files,
|
|
required AttachmentParentType currentContextType,
|
|
}) async {
|
|
if (files.isEmpty) return;
|
|
|
|
try {
|
|
for (var file in files) {
|
|
if (file.id == null) continue;
|
|
|
|
// 1. Capiamo quali collegamenti ha questo file attualmente
|
|
final currentLinks = {
|
|
AttachmentParentType.operation: file.operationId,
|
|
AttachmentParentType.ticket: file.ticketId,
|
|
AttachmentParentType.customer: file.customerId,
|
|
};
|
|
|
|
// 2. Simuliamo la rimozione del collegamento per il contesto attuale
|
|
currentLinks[currentContextType] = null;
|
|
|
|
// 3. Controlliamo se rimangono altri ID valorizzati
|
|
final hasOtherActiveLinks = currentLinks.values.any(
|
|
(id) => id != null && id.isNotEmpty,
|
|
);
|
|
|
|
if (hasOtherActiveLinks) {
|
|
// A. Ci sono ancora altre entità che usano questo file!
|
|
// Scolleghiamolo SOLO dal contesto attuale mettendo a NULL la sua colonna
|
|
await _supabase
|
|
.from(_tableName)
|
|
.update({currentContextType.dbColumn: null})
|
|
.eq('id', file.id!);
|
|
} else {
|
|
// B. Nessuno usa più questo file! ELIMINAZIONE FISICA TOTALE.
|
|
await _supabase.from(_tableName).delete().eq('id', file.id!);
|
|
|
|
if (file.storagePath != null) {
|
|
await _supabase.storage.from(_bucketName).remove([
|
|
file.storagePath!,
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
} catch (e) {
|
|
throw Exception("Errore nell'eliminazione dei file: $e");
|
|
}
|
|
}
|
|
|
|
/// RINOMINA UN FILE (Solo nel DB, non cambiamo il file fisico)
|
|
Future<void> renameAttachment(String fileId, String newName) async {
|
|
try {
|
|
await _supabase
|
|
.from(_tableName)
|
|
.update({'name': newName})
|
|
.eq('id', fileId);
|
|
} catch (e) {
|
|
throw Exception("Errore nella rinomina del file: $e");
|
|
}
|
|
}
|
|
|
|
/// ASSOCIA UN FILE A UN'ALTRA ENTITÀ (Modifica il record esistente)
|
|
Future<void> linkFileToEntity({
|
|
required String fileId,
|
|
required AttachmentParentType targetType,
|
|
required String targetId,
|
|
}) async {
|
|
try {
|
|
// Facciamo un semplice UPDATE aggiungendo l'ID nella colonna giusta
|
|
await _supabase
|
|
.from(_tableName)
|
|
.update({targetType.dbColumn: targetId})
|
|
.eq('id', fileId);
|
|
} catch (e) {
|
|
throw Exception("Errore nel collegamento del file: $e");
|
|
}
|
|
}
|
|
|
|
// Helper per indovinare il content-type base
|
|
String _guessContentType(String extension) {
|
|
switch (extension.toLowerCase()) {
|
|
case 'pdf':
|
|
return 'application/pdf';
|
|
case 'png':
|
|
return 'image/png';
|
|
case 'jpg':
|
|
case 'jpeg':
|
|
return 'image/jpeg';
|
|
default:
|
|
return 'application/octet-stream';
|
|
}
|
|
}
|
|
}
|