151 lines
5.8 KiB
Dart
151 lines
5.8 KiB
Dart
|
|
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);
|
||
|
|
}
|
||
|
|
}
|