refactor: Improve code formatting and readability in database and note repository
This commit is contained in:
+43
-14
@@ -11,8 +11,10 @@ part 'app_database.g.dart';
|
|||||||
class Categories extends Table {
|
class Categories extends Table {
|
||||||
TextColumn get uuid => text().unique()();
|
TextColumn get uuid => text().unique()();
|
||||||
TextColumn get encryptedName => text().named('encrypted_name')();
|
TextColumn get encryptedName => text().named('encrypted_name')();
|
||||||
IntColumn get serverVersion => integer().named('server_version').withDefault(const Constant(0))();
|
IntColumn get serverVersion =>
|
||||||
BoolColumn get isDeleted => boolean().named('is_deleted').withDefault(const Constant(false))();
|
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')();
|
DateTimeColumn get updatedAt => dateTime().named('updated_at')();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -28,8 +30,10 @@ class Notes extends Table {
|
|||||||
DateTimeColumn get createdAt => dateTime().named('created_at')();
|
DateTimeColumn get createdAt => dateTime().named('created_at')();
|
||||||
DateTimeColumn get updatedAt => dateTime().named('updated_at')();
|
DateTimeColumn get updatedAt => dateTime().named('updated_at')();
|
||||||
IntColumn get sortIndex => integer().named('sort_index')();
|
IntColumn get sortIndex => integer().named('sort_index')();
|
||||||
IntColumn get serverVersion => integer().named('server_version').withDefault(const Constant(0))();
|
IntColumn get serverVersion =>
|
||||||
BoolColumn get isDeleted => boolean().named('is_deleted').withDefault(const Constant(false))();
|
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')();
|
TextColumn get categoryId => text().nullable().named('category_id')();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,7 +41,8 @@ class Notes extends Table {
|
|||||||
class AppDatabase extends _$AppDatabase {
|
class AppDatabase extends _$AppDatabase {
|
||||||
@override
|
@override
|
||||||
int get schemaVersion => 1;
|
int get schemaVersion => 1;
|
||||||
AppDatabase({required String encryptionKey}) : super(_openConnection(encryptionKey));
|
AppDatabase({required String encryptionKey})
|
||||||
|
: super(_openConnection(encryptionKey));
|
||||||
|
|
||||||
// ========== Categories ==========
|
// ========== Categories ==========
|
||||||
Future<List<DbCategory>> getAllCategories() {
|
Future<List<DbCategory>> getAllCategories() {
|
||||||
@@ -49,8 +54,9 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteCategory(String uuid) {
|
Future<void> deleteCategory(String uuid) {
|
||||||
return (update(categories)..where((c) => c.uuid.equals(uuid)))
|
return (update(categories)..where((c) => c.uuid.equals(uuid))).write(
|
||||||
.write(CategoriesCompanion(isDeleted: Value(true)));
|
CategoriesCompanion(isDeleted: Value(true)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Notes ==========
|
// ========== Notes ==========
|
||||||
@@ -63,7 +69,9 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
|
|
||||||
Future<int> insertNoteAtTop(NotesCompanion note) {
|
Future<int> insertNoteAtTop(NotesCompanion note) {
|
||||||
return transaction(() async {
|
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<int>(0)));
|
return into(notes).insert(note.copyWith(sortIndex: const Value<int>(0)));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -83,7 +91,12 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteNote(int id, int removedIndex) async {
|
Future<void> 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(
|
await customStatement(
|
||||||
'UPDATE notes SET sort_index = sort_index - 1 WHERE sort_index > ? AND is_deleted = 0',
|
'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(
|
await customStatement(
|
||||||
'UPDATE notes SET sort_index = ? WHERE id = ?',
|
'UPDATE notes SET sort_index = ?, updated_at = ? WHERE id = ?',
|
||||||
[newIndex, id],
|
[newIndex, DateTime.now().toIso8601String(), id],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<DbNote>> getNotesChangedSince(DateTime since) {
|
||||||
|
return (select(
|
||||||
|
notes,
|
||||||
|
)..where((n) => n.updatedAt.isBiggerThanValue(since))).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<DbCategory>> getCategoriesChangedSince(DateTime since) {
|
||||||
|
return (select(
|
||||||
|
categories,
|
||||||
|
)..where((c) => c.updatedAt.isBiggerThanValue(since))).get();
|
||||||
|
}
|
||||||
|
|
||||||
// ========== Sync helpers ==========
|
// ========== Sync helpers ==========
|
||||||
Future<List<DbNote>> getUnsyncedNotes() {
|
Future<List<DbNote>> 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<List<DbCategory>> getUnsyncedCategories() {
|
Future<List<DbCategory>> 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) {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,14 +6,15 @@ import 'package:notas/models/note.dart';
|
|||||||
import 'package:notas/models/category.dart';
|
import 'package:notas/models/category.dart';
|
||||||
|
|
||||||
import 'package:notas/data/note_encryption.dart';
|
import 'package:notas/data/note_encryption.dart';
|
||||||
|
|
||||||
class NoteRepository {
|
class NoteRepository {
|
||||||
NoteRepository({
|
NoteRepository({
|
||||||
required AppDatabase database,
|
required AppDatabase database,
|
||||||
required AuthApi authApi,
|
required AuthApi authApi,
|
||||||
required String masterKey,
|
required String masterKey,
|
||||||
}) : _database = database,
|
}) : _database = database,
|
||||||
_authApi = authApi,
|
_authApi = authApi,
|
||||||
_masterKey = masterKey;
|
_masterKey = masterKey;
|
||||||
|
|
||||||
final AppDatabase _database;
|
final AppDatabase _database;
|
||||||
final AuthApi _authApi;
|
final AuthApi _authApi;
|
||||||
@@ -42,7 +43,9 @@ class NoteRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<Note> updateNote(Note note) async {
|
Future<Note> 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(
|
await _database.updateNoteRow(
|
||||||
DbNote(
|
DbNote(
|
||||||
@@ -63,16 +66,17 @@ class NoteRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteNote(Note note) async {
|
Future<void> 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(
|
await _database.deleteNoteAndShift(id: noteId, removedIndex: note.index);
|
||||||
id: noteId,
|
|
||||||
removedIndex: note.index,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> moveNote(Note note, int newIndex) async {
|
Future<void> 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(
|
await _database.moveNote(
|
||||||
id: noteId,
|
id: noteId,
|
||||||
@@ -89,19 +93,39 @@ class NoteRepository {
|
|||||||
try {
|
try {
|
||||||
// Get last sync timestamp
|
// Get last sync timestamp
|
||||||
final DateTime? lastSync = await _authApi.getLastSyncAt();
|
final DateTime? lastSync = await _authApi.getLastSyncAt();
|
||||||
final DateTime? lastSyncForRequest = forceFull ? DateTime.utc(1970, 1, 1) : lastSync;
|
final DateTime? lastSyncForRequest = forceFull
|
||||||
|
? DateTime.utc(1970, 1, 1)
|
||||||
// Collect pending changes
|
: lastSync;
|
||||||
final List<DbNote> unsyncedNotes = await _database.getUnsyncedNotes();
|
|
||||||
final List<DbCategory> unsyncedCategories = await _database.getUnsyncedCategories();
|
// 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<DbNote> unsyncedNotes;
|
||||||
|
final List<DbCategory> 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)
|
// Build sync request (note: we send encrypted data, but locally we have plaintext)
|
||||||
// Encrypt all notes before sending
|
// Encrypt all notes before sending
|
||||||
final List<SyncNotePayload> encryptedNotesPayload = [];
|
final List<SyncNotePayload> encryptedNotesPayload = [];
|
||||||
for (final dbNote in unsyncedNotes) {
|
for (final dbNote in unsyncedNotes) {
|
||||||
final note = _fromDbNote(dbNote);
|
final note = _fromDbNote(dbNote);
|
||||||
final encryptedTitle = await NoteEncryption.encryptNote(note.title, _masterKey);
|
final encryptedTitle = await NoteEncryption.encryptNote(
|
||||||
final encryptedBody = await NoteEncryption.encryptNote(note.body, _masterKey);
|
note.title,
|
||||||
|
_masterKey,
|
||||||
|
);
|
||||||
|
final encryptedBody = await NoteEncryption.encryptNote(
|
||||||
|
note.body,
|
||||||
|
_masterKey,
|
||||||
|
);
|
||||||
encryptedNotesPayload.add(
|
encryptedNotesPayload.add(
|
||||||
SyncNotePayload.fromNote(
|
SyncNotePayload.fromNote(
|
||||||
note,
|
note,
|
||||||
@@ -112,9 +136,7 @@ class NoteRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final List<SyncCategoryPayload> categoriesPayload = unsyncedCategories
|
final List<SyncCategoryPayload> categoriesPayload = unsyncedCategories
|
||||||
.map((cat) => SyncCategoryPayload.fromCategory(
|
.map((cat) => SyncCategoryPayload.fromCategory(_fromDbCategory(cat)))
|
||||||
_fromDbCategory(cat),
|
|
||||||
))
|
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
final SyncRequest syncRequest = SyncRequest(
|
final SyncRequest syncRequest = SyncRequest(
|
||||||
@@ -126,8 +148,7 @@ class NoteRepository {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Call sync API
|
// Call sync API
|
||||||
final Map<String, dynamic> syncResult =
|
final Map<String, dynamic> syncResult = await _authApi.sync(syncRequest);
|
||||||
await _authApi.sync(syncRequest);
|
|
||||||
|
|
||||||
if (syncResult['error'] == true) {
|
if (syncResult['error'] == true) {
|
||||||
return {'error': true, 'message': syncResult['body']};
|
return {'error': true, 'message': syncResult['body']};
|
||||||
@@ -154,7 +175,8 @@ class NoteRepository {
|
|||||||
|
|
||||||
Future<void> _applySyncResponse(SyncResponse response) async {
|
Future<void> _applySyncResponse(SyncResponse response) async {
|
||||||
// Apply categories from server
|
// Apply categories from server
|
||||||
for (final SyncCategoryResponse catResponse in response.changes.categories) {
|
for (final SyncCategoryResponse catResponse
|
||||||
|
in response.changes.categories) {
|
||||||
await _database.upsertCategory(
|
await _database.upsertCategory(
|
||||||
CategoriesCompanion(
|
CategoriesCompanion(
|
||||||
uuid: Value(catResponse.id),
|
uuid: Value(catResponse.id),
|
||||||
@@ -168,9 +190,9 @@ class NoteRepository {
|
|||||||
|
|
||||||
// Apply notes from server
|
// Apply notes from server
|
||||||
for (final SyncNoteResponse noteResponse in response.changes.notes) {
|
for (final SyncNoteResponse noteResponse in response.changes.notes) {
|
||||||
final existingNote = await (_database.select(_database.notes)
|
final existingNote = await (_database.select(
|
||||||
..where((n) => n.uuid.equals(noteResponse.id)))
|
_database.notes,
|
||||||
.getSingleOrNull();
|
)..where((n) => n.uuid.equals(noteResponse.id))).getSingleOrNull();
|
||||||
|
|
||||||
// Decrypt note content
|
// Decrypt note content
|
||||||
String decryptedTitle = 'Encrypted';
|
String decryptedTitle = 'Encrypted';
|
||||||
@@ -207,7 +229,9 @@ class NoteRepository {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Insert new note
|
// Insert new note
|
||||||
await _database.into(_database.notes).insert(
|
await _database
|
||||||
|
.into(_database.notes)
|
||||||
|
.insert(
|
||||||
NotesCompanion(
|
NotesCompanion(
|
||||||
uuid: Value(noteResponse.id),
|
uuid: Value(noteResponse.id),
|
||||||
title: Value(decryptedTitle),
|
title: Value(decryptedTitle),
|
||||||
|
|||||||
Reference in New Issue
Block a user