From 69096eb49428ca7585ed132efca4d4665a3d161f Mon Sep 17 00:00:00 2001 From: SMSourov <81343486+SMSourov@users.noreply.github.com> Date: Sat, 7 Oct 2023 18:25:02 +0600 Subject: [PATCH] Added crud for notes --- lib/services/crud/crud_exceptions.dart | 17 ++ lib/services/crud/notes_service.dart | 258 ++++++++++++++++++ macos/Flutter/GeneratedPluginRegistrant.swift | 4 + pubspec.lock | 108 +++++++- pubspec.yaml | 3 + 5 files changed, 388 insertions(+), 2 deletions(-) create mode 100644 lib/services/crud/crud_exceptions.dart create mode 100644 lib/services/crud/notes_service.dart diff --git a/lib/services/crud/crud_exceptions.dart b/lib/services/crud/crud_exceptions.dart new file mode 100644 index 0000000..1863c1c --- /dev/null +++ b/lib/services/crud/crud_exceptions.dart @@ -0,0 +1,17 @@ +class DatabaseAlreadyOpenException implements Exception {} + +class UnableToGetDocumentsDirectory implements Exception {} + +class DatabaseIsNotOpen implements Exception {} + +class CouldNotDeleteUser implements Exception {} + +class UserAlreadyExists implements Exception {} + +class CouldNotFindUser implements Exception {} + +class CouldNotDeleteNote implements Exception {} + +class CouldNotFindNotes implements Exception {} + +class CouldNotUpdateNote implements Exception {} diff --git a/lib/services/crud/notes_service.dart b/lib/services/crud/notes_service.dart new file mode 100644 index 0000000..bee96e7 --- /dev/null +++ b/lib/services/crud/notes_service.dart @@ -0,0 +1,258 @@ +import 'package:cleanifi/services/crud/crud_exceptions.dart'; +import 'package:flutter/material.dart'; +import 'package:sqflite/sqflite.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:path/path.dart' show join; + +class NotesService { + Database? _db; + + Future updateNote({ + required DatabaseNote note, + required String text, + }) async { + final db = _getDatabaseOrThrow(); + + await getNote(id: note.id); + + final updatesCount = await db.update(noteTable, { + textColumn: text, + isSyncedWithCloudColumn: 0, + }); + + if (updatesCount == 0) { + throw CouldNotUpdateNote; + } else { + return await getNote(id: note.id); + } + } + + Future> getAllNotes() async { + final db = _getDatabaseOrThrow(); + final notes = await db.query(noteTable); + + return notes.map((noteRow) => DatabaseNote.fromRow(noteRow)); + } + + Future getNote({required int id}) async { + final db = _getDatabaseOrThrow(); + final notes = await db.query( + noteTable, + limit: 1, + where: "id = ?", + whereArgs: [id], + ); + + if (notes.isEmpty) { + throw CouldNotFindNotes(); + } else { + return DatabaseNote.fromRow(notes.first); + } + } + + Future deleteAllNotes() async { + final db = _getDatabaseOrThrow(); + return await db.delete(noteTable); + } + + Future deleteNote({required int id}) async { + final db = _getDatabaseOrThrow(); + final deletedCount = await db.delete( + noteTable, + where: 'id = ?', + whereArgs: [id], + ); + if (deletedCount == 0) { + throw CouldNotDeleteNote(); + } + } + + Future createNote({required DatabaseUser owner}) async { + final db = _getDatabaseOrThrow(); + + // make sure owner exists in the database with the correct id + final dbUser = await getUser(email: owner.email); + if (dbUser != owner) { + throw CouldNotFindUser(); + } + + const text = ""; + // create the note + final noteId = await db.insert(noteTable, { + userIdColumn: owner.id, + textColumn: text, + isSyncedWithCloudColumn: 1, + }); + + final note = DatabaseNote( + id: noteId, userId: owner.id, text: text, isSyncedWithCloud: true); + return note; + } + + Future getUser({required String email}) async { + final db = _getDatabaseOrThrow(); + + final results = await db.query( + userTable, + limit: 1, + where: "email = ?", + whereArgs: [email.toLowerCase()], + ); + if (results.isEmpty) { + throw CouldNotFindUser; + } else { + return DatabaseUser.fromRow(results.first); + } + } + + Future createUser({required String email}) async { + final db = _getDatabaseOrThrow(); + final results = await db.query( + userTable, + limit: 1, + where: "email = ?", + whereArgs: [email.toLowerCase()], + ); + if (results.isNotEmpty) { + throw UserAlreadyExists(); + } + + final userID = await db.insert(userTable, { + emailColumn: email.toLowerCase(), + }); + + return DatabaseUser( + id: userID, + email: email, + ); + } + + Future deleteUser({required String email}) async { + final db = _getDatabaseOrThrow(); + final deletedCount = await db.delete( + userTable, + where: "email = ?", + whereArgs: [email.toLowerCase()], + ); + if (deletedCount != 1) { + throw CouldNotDeleteUser(); + } + } + + Database _getDatabaseOrThrow() { + final db = _db; + if (db == null) { + throw DatabaseIsNotOpen(); + } else { + return db; + } + } + + Future close() async { + final db = _db; + if (db == null) { + throw DatabaseIsNotOpen(); + } else { + await db.close(); + _db = null; + } + } + + Future open() async { + if (_db != null) { + throw DatabaseAlreadyOpenException(); + } + try { + final docsPath = await getApplicationDocumentsDirectory(); + final dbPath = join(docsPath.path, dbName); + final db = await openDatabase(dbPath); + _db = db; + // create user table + await db.execute(createUserTable); + // create note table + await db.execute(createNoteTable); + } on MissingPlatformDirectoryException { + throw UnableToGetDocumentsDirectory(); + } + } +} + +@immutable +class DatabaseUser { + final int id; + final String email; + const DatabaseUser({ + required this.id, + required this.email, + }); + + DatabaseUser.fromRow(Map map) + : id = map[idColumn] as int, + email = map[emailColumn] as String; + + @override + String toString() => "Person, ID = $id, email = $email"; + + @override + bool operator ==(covariant DatabaseUser other) => id == other.id; + + @override + int get hashCode => id.hashCode; +} + +class DatabaseNote { + final int id; + final int userId; + final String text; + final bool isSyncedWithCloud; + + DatabaseNote({ + required this.id, + required this.userId, + required this.text, + required this.isSyncedWithCloud, + }); + + DatabaseNote.fromRow(Map map) + : id = map[idColumn] as int, + userId = map[userIdColumn] as int, + text = map[textColumn] as String, + isSyncedWithCloud = + (map[isSyncedWithCloudColumn] as int) == 1 ? true : false; + + @override + String toString() => + "Note, ID = $id, userId = $userId, isSyncedWithCloud = $isSyncedWithCloud, text = $text"; + + @override + bool operator ==(covariant DatabaseNote other) => id == other.id; + + @override + int get hashCode => id.hashCode; +} + +const dbName = "notes.db"; +const noteTable = "note"; +const userTable = "user"; +const idColumn = "id"; +const emailColumn = "email"; +const userIdColumn = "user_id"; +const textColumn = "text"; +const isSyncedWithCloudColumn = "is_synced_with_cloud"; +const createUserTable = ''' + CREATE TABLE IF NOT EXISTS "user" ( + "id" INTEGER NOT NULL, + "email" TEXT NOT NULL UNIQUE, + PRIMARY KEY("id" AUTOINCREMENT) + ); +'''; +const createNoteTable = ''' + CREATE TABLE IF NOT EXISTS "note" ( + "id" INTEGER NOT NULL, + "user_id" INTEGER NOT NULL, + "text" INTEGER, + "is_synced_with_cloud" INTEGER NOT NULL DEFAULT 0, + FOREIGN KEY("user_id") REFERENCES "user"("id"), + PRIMARY KEY("id" AUTOINCREMENT) + ); +'''; diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 44ea85b..76ab4c5 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -9,10 +9,14 @@ import cloud_firestore import firebase_analytics import firebase_auth import firebase_core +import path_provider_foundation +import sqflite func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FLTFirebaseFirestorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseFirestorePlugin")) FLTFirebaseAnalyticsPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAnalyticsPlugin")) FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin")) FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) } diff --git a/pubspec.lock b/pubspec.lock index cc0d632..5a1635e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -137,6 +137,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" + url: "https://pub.dev" + source: hosted + version: "2.1.0" file: dependency: transitive description: @@ -353,13 +361,69 @@ packages: source: hosted version: "2.1.0" path: - dependency: transitive + dependency: "direct main" description: name: path sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" url: "https://pub.dev" source: hosted version: "1.8.3" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa + url: "https://pub.dev" + source: hosted + version: "2.1.1" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "6b8b19bd80da4f11ce91b2d1fb931f3006911477cec227cce23d3253d80df3f1" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" + url: "https://pub.dev" + source: hosted + version: "2.3.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + url: "https://pub.dev" + source: hosted + version: "2.2.1" + platform: + dependency: transitive + description: + name: platform + sha256: ae68c7bfcd7383af3629daafb32fb4e8681c7154428da4febcff06200585f102 + url: "https://pub.dev" + source: hosted + version: "3.1.2" plugin_platform_interface: dependency: transitive description: @@ -445,6 +509,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.0" + sqflite: + dependency: "direct main" + description: + name: sqflite + sha256: "591f1602816e9c31377d5f008c2d9ef7b8aca8941c3f89cc5fd9d84da0c38a9a" + url: "https://pub.dev" + source: hosted + version: "2.3.0" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "1b92f368f44b0dee2425bb861cfa17b6f6cf3961f762ff6f941d20b33355660a" + url: "https://pub.dev" + source: hosted + version: "2.5.0" stack_trace: dependency: transitive description: @@ -469,6 +549,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "5fcbd27688af6082f5abd611af56ee575342c30e87541d0245f7ff99faa02c60" + url: "https://pub.dev" + source: hosted + version: "3.1.0" term_glyph: dependency: transitive description: @@ -557,6 +645,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.1" + win32: + dependency: transitive + description: + name: win32 + sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3" + url: "https://pub.dev" + source: hosted + version: "5.0.9" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2" + url: "https://pub.dev" + source: hosted + version: "1.0.3" yaml: dependency: transitive description: @@ -567,4 +671,4 @@ packages: version: "3.1.2" sdks: dart: ">=3.1.0 <4.0.0" - flutter: ">=3.3.0" + flutter: ">=3.7.0" diff --git a/pubspec.yaml b/pubspec.yaml index 72a54b0..39e1656 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -39,6 +39,9 @@ dependencies: firebase_auth: ^4.9.0 cloud_firestore: ^4.9.1 firebase_analytics: ^10.4.5 + sqflite: ^2.3.0 + path_provider: ^2.1.1 + path: ^1.8.3 dev_dependencies: flutter_test: