notes
This commit is contained in:
150
lib/features/notes/data/notes_repository.dart
Normal file
150
lib/features/notes/data/notes_repository.dart
Normal file
@@ -0,0 +1,150 @@
|
||||
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
|
||||
// Se ci sono collaboratori da inserire, li prepariamo in blocco (Bulk Insert)
|
||||
if (note.collaboratorIds.isNotEmpty) {
|
||||
final collaboratorsToInsert = note.collaboratorIds
|
||||
.map((staffId) => {'note_id': noteId, 'staff_id': staffId})
|
||||
.toList();
|
||||
|
||||
await _supabase.from('note_collaborators').insert(collaboratorsToInsert);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user