Files
flux/lib/features/notes/data/notes_repository.dart

165 lines
6.2 KiB
Dart
Raw Permalink Normal View History

2026-05-28 23:48:30 +02:00
import 'package:flutter/foundation.dart';
2026-05-21 14:43:47 +02:00
import 'package:flux/core/blocs/session/session_cubit.dart';
import 'package:flux/core/enums_and_consts/consts.dart';
import 'package:flux/features/notes/models/note_model.dart';
import 'package:get_it/get_it.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
class NotesRepository {
final _supabase = GetIt.I.get<SupabaseClient>();
String get _companyId => GetIt.I.get<SessionCubit>().state.company!.id!;
String get _currentStaffId =>
GetIt.I.get<SessionCubit>().state.currentStaffMember!.id!;
/// Recupera tutte le note visibili dall'utente corrente:
/// 1. Note create da lui
/// 2. Note condivise a tutti (is_shared_all = true)
/// 3. Note in cui l'utente è esplicitamente un collaboratore
Future<List<NoteModel>> getNotes() async {
try {
// Usiamo la sintassi avanzata di Supabase per fare il filtro OR sulle relazioni
// Inoltre tiriamo giù note_collaborators e l'anagrafica dei membri dello staff associati
final response = await _supabase
.from(Tables.notes)
.select('''
*,
${Tables.noteCollaborators}!inner (
staff_id,
${Tables.staffMembers} (*)
)
''')
// Filtro multi-tenant di sicurezza (già ridondante con RLS ma ottimo per performance)
.eq('company_id', _companyId)
// Questa è la magia: l'utente vede la nota se è sua, se è pubblica o se è tra i collaboratori
.or(
'created_by.eq.$_currentStaffId,is_shared_all.eq.true,note_collaborators.staff_id.eq.$_currentStaffId',
)
// Ordiniamo prima per sticky (pinned) e poi per data di aggiornamento
.order('is_pinned', ascending: false)
.order('updated_at', ascending: false);
return (response as List)
.map((json) => NoteModel.fromMap(json as Map<String, dynamic>))
.toList();
} catch (e) {
// In caso di errore sulla join !inner se non ci sono collaboratori,
// facciamo un fallback pulito su una query standard e uniamo i dati.
return _getNotesFallback();
}
}
/// Fallback sicuro nel caso la query complessa con !inner si inceppi se la lista collaboratori è vuota
Future<List<NoteModel>> _getNotesFallback() async {
final response = await _supabase
.from(Tables.notes)
.select('''
*,
${Tables.noteCollaborators} (
staff_id,
${Tables.staffMembers} (*)
)
''')
.eq('company_id', _companyId)
.order('is_pinned', ascending: false)
.order('updated_at', ascending: false);
final allNotes = (response as List)
.map((json) => NoteModel.fromMap(json as Map<String, dynamic>))
.toList();
// Filtriamo lato codice per essere sicuri della visibilità
return allNotes.where((note) {
return note.createdBy == _currentStaffId ||
note.isSharedAll ||
note.collaboratorIds.contains(_currentStaffId);
}).toList();
}
Stream<List<NoteModel>> notesStream({
required String companyId,
required String currentStaffId,
}) {
return _supabase
.from(Tables.notes)
.stream(primaryKey: ['id'])
.eq('company_id', companyId)
.order('is_pinned', ascending: false)
// Nota: puoi ordinare solo per un campo alla volta nello stream nativo di Supabase.
// Ordiniamo per is_pinned, l'ordinamento per data lo facciamo al volo in RAM se serve,
// oppure ordiniamo per updated_at e il pin lo gestiamo via software.
.map((rawNotes) {
return rawNotes.map((json) => NoteModel.fromMap(json)).where((note) {
// Filtro multi-tenant di sicurezza in memoria
return note.createdBy == currentStaffId ||
note.isSharedAll ||
note.collaboratorIds.contains(currentStaffId);
}).toList();
});
}
Future<String> saveNote(NoteModel note) async {
// 1. Eseguiamo l'upsert sulla tabella principale 'notes'
// Supabase gestisce in automatico: se l'id è null inserisce, se c'è fa update.
// Usiamo il .select().single() per farci restituire l'id generato (in caso di nuova nota)
final noteResponse = await _supabase
.from(Tables.notes)
.upsert({
if (note.id != null) 'id': note.id,
'company_id': note.companyId,
'created_by': note.createdBy,
'title': note.title,
'content': note.content,
'color': note.color,
'is_pinned': note.isPinned,
'is_shared_all': note.isSharedAll,
'updated_at': DateTime.now().toIso8601String(),
})
.select('id')
.single();
final noteId = noteResponse['id'] as String;
// Se la nota è condivisa con tutti, spazziamo via eventuali collaboratori singoli e usciamo
if (note.isSharedAll) {
await _supabase.from('note_collaborators').delete().eq('note_id', noteId);
return noteId;
}
// 2. LA STRATEGIA DEL PIAZZA PULITA SUI COLLABORATORI
// Prima di tutto eliminiamo TUTTI i collaboratori attuali per questa specifica nota
await _supabase.from('note_collaborators').delete().eq('note_id', noteId);
// 3. RE-INSERIMENTO DELLA LISTA AGGIORNATA
if (note.collaboratorIds.isNotEmpty) {
final collaboratorsToInsert = note.collaboratorIds
2026-05-28 23:48:30 +02:00
.map(
(staffId) => {
'note_id': noteId,
'staff_id': staffId,
'company_id': note.companyId, // Aggiunto questo!
},
)
2026-05-21 14:43:47 +02:00
.toList();
2026-05-28 23:48:30 +02:00
// Consiglio da pro: avvolgi l'insert in un try-catch per stampare l'errore esatto a console
try {
await _supabase
.from(Tables.noteCollaborators)
.insert(collaboratorsToInsert);
} catch (e) {
debugPrint('Errore inserimento collaboratori: $e');
throw Exception('Impossibile aggiungere i collaboratori alla nota.');
}
2026-05-21 14:43:47 +02:00
}
// Restituiamo l'id alla UI (fondamentale per la nostra logica Ninja di creazione)
return noteId;
}
/// Elimina una nota (i collaboratori si cancellano in cascata grazie al vincolo del DB)
Future<void> deleteNote(String noteId) async {
await _supabase.from(Tables.notes).delete().eq('id', noteId);
}
}