From bb8caeef93d8756b0499e1ad62c9974a4f50d93c Mon Sep 17 00:00:00 2001 From: Marcos Date: Tue, 19 May 2026 09:11:52 +0200 Subject: [PATCH] refactor: Improve code formatting and readability in database and note repository --- lib/data/app_database.dart | 57 +++++++++++++++++++------ lib/data/note_repository.dart | 80 +++++++++++++++++++++++------------ 2 files changed, 95 insertions(+), 42 deletions(-) diff --git a/lib/data/app_database.dart b/lib/data/app_database.dart index 0bc6b55..db14b55 100644 --- a/lib/data/app_database.dart +++ b/lib/data/app_database.dart @@ -11,8 +11,10 @@ part 'app_database.g.dart'; class Categories extends Table { TextColumn get uuid => text().unique()(); TextColumn get encryptedName => text().named('encrypted_name')(); - IntColumn get serverVersion => integer().named('server_version').withDefault(const Constant(0))(); - BoolColumn get isDeleted => boolean().named('is_deleted').withDefault(const Constant(false))(); + IntColumn get serverVersion => + integer().named('server_version').withDefault(const Constant(0))(); + BoolColumn get isDeleted => + boolean().named('is_deleted').withDefault(const Constant(false))(); DateTimeColumn get updatedAt => dateTime().named('updated_at')(); @override @@ -28,8 +30,10 @@ class Notes extends Table { DateTimeColumn get createdAt => dateTime().named('created_at')(); DateTimeColumn get updatedAt => dateTime().named('updated_at')(); IntColumn get sortIndex => integer().named('sort_index')(); - IntColumn get serverVersion => integer().named('server_version').withDefault(const Constant(0))(); - BoolColumn get isDeleted => boolean().named('is_deleted').withDefault(const Constant(false))(); + IntColumn get serverVersion => + integer().named('server_version').withDefault(const Constant(0))(); + BoolColumn get isDeleted => + boolean().named('is_deleted').withDefault(const Constant(false))(); TextColumn get categoryId => text().nullable().named('category_id')(); } @@ -37,7 +41,8 @@ class Notes extends Table { class AppDatabase extends _$AppDatabase { @override int get schemaVersion => 1; - AppDatabase({required String encryptionKey}) : super(_openConnection(encryptionKey)); + AppDatabase({required String encryptionKey}) + : super(_openConnection(encryptionKey)); // ========== Categories ========== Future> getAllCategories() { @@ -49,8 +54,9 @@ class AppDatabase extends _$AppDatabase { } Future deleteCategory(String uuid) { - return (update(categories)..where((c) => c.uuid.equals(uuid))) - .write(CategoriesCompanion(isDeleted: Value(true))); + return (update(categories)..where((c) => c.uuid.equals(uuid))).write( + CategoriesCompanion(isDeleted: Value(true)), + ); } // ========== Notes ========== @@ -63,7 +69,9 @@ class AppDatabase extends _$AppDatabase { Future insertNoteAtTop(NotesCompanion note) { return transaction(() async { - await customStatement('UPDATE notes SET sort_index = sort_index + 1 WHERE is_deleted = 0'); + await customStatement( + 'UPDATE notes SET sort_index = sort_index + 1 WHERE is_deleted = 0', + ); return into(notes).insert(note.copyWith(sortIndex: const Value(0))); }); } @@ -83,7 +91,12 @@ class AppDatabase extends _$AppDatabase { } Future deleteNote(int id, int removedIndex) async { - await (update(notes)..where((n) => n.id.equals(id))).write(NotesCompanion(isDeleted: Value(true))); + await (update(notes)..where((n) => n.id.equals(id))).write( + NotesCompanion( + isDeleted: const Value(true), + updatedAt: Value(DateTime.now()), + ), + ); await customStatement( 'UPDATE notes SET sort_index = sort_index - 1 WHERE sort_index > ? AND is_deleted = 0', @@ -121,19 +134,35 @@ class AppDatabase extends _$AppDatabase { } await customStatement( - 'UPDATE notes SET sort_index = ? WHERE id = ?', - [newIndex, id], + 'UPDATE notes SET sort_index = ?, updated_at = ? WHERE id = ?', + [newIndex, DateTime.now().toIso8601String(), id], ); }); } + Future> getNotesChangedSince(DateTime since) { + return (select( + notes, + )..where((n) => n.updatedAt.isBiggerThanValue(since))).get(); + } + + Future> getCategoriesChangedSince(DateTime since) { + return (select( + categories, + )..where((c) => c.updatedAt.isBiggerThanValue(since))).get(); + } + // ========== Sync helpers ========== Future> getUnsyncedNotes() { - return (select(notes)..where((n) => n.isDeleted.equals(true) | n.serverVersion.equals(0))).get(); + return (select(notes) + ..where((n) => n.isDeleted.equals(true) | n.serverVersion.equals(0))) + .get(); } Future> getUnsyncedCategories() { - return (select(categories)..where((c) => c.isDeleted.equals(true) | c.serverVersion.equals(0))).get(); + return (select(categories) + ..where((c) => c.isDeleted.equals(true) | c.serverVersion.equals(0))) + .get(); } } @@ -154,4 +183,4 @@ LazyDatabase _openConnection(String encryptionKey) { }, ); }); -} \ No newline at end of file +} diff --git a/lib/data/note_repository.dart b/lib/data/note_repository.dart index 9282dec..84b6b73 100644 --- a/lib/data/note_repository.dart +++ b/lib/data/note_repository.dart @@ -6,14 +6,15 @@ import 'package:notas/models/note.dart'; import 'package:notas/models/category.dart'; import 'package:notas/data/note_encryption.dart'; + class NoteRepository { NoteRepository({ required AppDatabase database, required AuthApi authApi, - required String masterKey, - }) : _database = database, - _authApi = authApi, - _masterKey = masterKey; + required String masterKey, + }) : _database = database, + _authApi = authApi, + _masterKey = masterKey; final AppDatabase _database; final AuthApi _authApi; @@ -42,7 +43,9 @@ class NoteRepository { } Future updateNote(Note note) async { - final int noteId = note.id ?? (throw ArgumentError('Note id is required to update a note.')); + final int noteId = + note.id ?? + (throw ArgumentError('Note id is required to update a note.')); await _database.updateNoteRow( DbNote( @@ -63,16 +66,17 @@ class NoteRepository { } Future deleteNote(Note note) async { - final int noteId = note.id ?? (throw ArgumentError('Note id is required to delete a note.')); + final int noteId = + note.id ?? + (throw ArgumentError('Note id is required to delete a note.')); - await _database.deleteNoteAndShift( - id: noteId, - removedIndex: note.index, - ); + await _database.deleteNoteAndShift(id: noteId, removedIndex: note.index); } Future moveNote(Note note, int newIndex) async { - final int noteId = note.id ?? (throw ArgumentError('Note id is required to reorder a note.')); + final int noteId = + note.id ?? + (throw ArgumentError('Note id is required to reorder a note.')); await _database.moveNote( id: noteId, @@ -89,19 +93,39 @@ class NoteRepository { try { // Get last sync timestamp final DateTime? lastSync = await _authApi.getLastSyncAt(); - final DateTime? lastSyncForRequest = forceFull ? DateTime.utc(1970, 1, 1) : lastSync; - - // Collect pending changes - final List unsyncedNotes = await _database.getUnsyncedNotes(); - final List unsyncedCategories = await _database.getUnsyncedCategories(); + final DateTime? lastSyncForRequest = forceFull + ? DateTime.utc(1970, 1, 1) + : lastSync; + + // Collect pending local changes. + // If we already synced at least once, use updatedAt to avoid re-sending + // old notes that were already uploaded. + final List unsyncedNotes; + final List unsyncedCategories; + + if (forceFull || lastSync == null) { + unsyncedNotes = await _database.getUnsyncedNotes(); + unsyncedCategories = await _database.getUnsyncedCategories(); + } else { + unsyncedNotes = await _database.getNotesChangedSince(lastSync); + unsyncedCategories = await _database.getCategoriesChangedSince( + lastSync, + ); + } // Build sync request (note: we send encrypted data, but locally we have plaintext) // Encrypt all notes before sending final List encryptedNotesPayload = []; for (final dbNote in unsyncedNotes) { final note = _fromDbNote(dbNote); - final encryptedTitle = await NoteEncryption.encryptNote(note.title, _masterKey); - final encryptedBody = await NoteEncryption.encryptNote(note.body, _masterKey); + final encryptedTitle = await NoteEncryption.encryptNote( + note.title, + _masterKey, + ); + final encryptedBody = await NoteEncryption.encryptNote( + note.body, + _masterKey, + ); encryptedNotesPayload.add( SyncNotePayload.fromNote( note, @@ -112,9 +136,7 @@ class NoteRepository { } final List categoriesPayload = unsyncedCategories - .map((cat) => SyncCategoryPayload.fromCategory( - _fromDbCategory(cat), - )) + .map((cat) => SyncCategoryPayload.fromCategory(_fromDbCategory(cat))) .toList(); final SyncRequest syncRequest = SyncRequest( @@ -126,8 +148,7 @@ class NoteRepository { ); // Call sync API - final Map syncResult = - await _authApi.sync(syncRequest); + final Map syncResult = await _authApi.sync(syncRequest); if (syncResult['error'] == true) { return {'error': true, 'message': syncResult['body']}; @@ -154,7 +175,8 @@ class NoteRepository { Future _applySyncResponse(SyncResponse response) async { // Apply categories from server - for (final SyncCategoryResponse catResponse in response.changes.categories) { + for (final SyncCategoryResponse catResponse + in response.changes.categories) { await _database.upsertCategory( CategoriesCompanion( uuid: Value(catResponse.id), @@ -168,9 +190,9 @@ class NoteRepository { // Apply notes from server for (final SyncNoteResponse noteResponse in response.changes.notes) { - final existingNote = await (_database.select(_database.notes) - ..where((n) => n.uuid.equals(noteResponse.id))) - .getSingleOrNull(); + final existingNote = await (_database.select( + _database.notes, + )..where((n) => n.uuid.equals(noteResponse.id))).getSingleOrNull(); // Decrypt note content String decryptedTitle = 'Encrypted'; @@ -207,7 +229,9 @@ class NoteRepository { ); } else { // Insert new note - await _database.into(_database.notes).insert( + await _database + .into(_database.notes) + .insert( NotesCompanion( uuid: Value(noteResponse.id), title: Value(decryptedTitle),