Commit e30ebb2

derdilla <82763757+NobodyForNothing@users.noreply.github.com>
2024-03-26 13:24:26
implement MedicineRepository
Signed-off-by: derdilla <82763757+NobodyForNothing@users.noreply.github.com>
1 parent 2a3584f
Changed files (4)
health_data_store
health_data_store/lib/src/repositories/medicine_repository.dart
@@ -1,33 +1,67 @@
 import 'package:health_data_store/src/database_manager.dart';
-import 'package:health_data_store/src/repositories/repository.dart';
-import 'package:health_data_store/src/types/date_range.dart';
 import 'package:health_data_store/src/types/medicine.dart';
 import 'package:sqflite_common/sqflite.dart';
 
 /// Repository for medicines that are taken by the user.
-class MedicineRepository extends Repository<Medicine> {
+class MedicineRepository {
   /// Create the medicine repository.
   MedicineRepository(this._db);
 
   /// The [DatabaseManager] managed database.
   final Database _db;
 
-  @override
-  Future<void> add(Medicine value) {
-    // TODO: implement add
-    throw UnimplementedError();
-  }
+  /// Store a [Medicine] in the repository.
+  Future<void> add(Medicine medicine) => _db.transaction((txn) async {
+    final idRes = await txn.query('Medicine', columns: ['MAX(medID)']);
+    final id = (idRes.firstOrNull?['MAX(medID)']?.castOrNull<int>() ?? 0) + 1;
+    await txn.insert('Medicine', {
+      'medID': id,
+      'designation': medicine.designation,
+      'defaultDose': medicine.dosis,
+      'color': medicine.color,
+      'removed': 0,
+    });
+  });
 
-  @override
-  Future<List<Medicine>> get(DateRange range) {
-    // TODO: implement get
-    throw UnimplementedError();
-  }
+  /// Get a list of all stored Medicines that haven't been marked as removed.
+  Future<List<Medicine>> getAll() async {
+    final medData = await _db.query('Medicine',
+      columns: ['designation', 'defaultDose', 'color'],
+      where: 'removed = false'
+    );
+    final meds = <Medicine>[];
+    for (final m in medData) {
+      meds.add(Medicine(
+        designation: m['designation'].toString(),
+        dosis: m['defaultDose'] as double?,
+        color: m['color'] as int?,
+      ));
+    }
 
-  @override
-  Future<void> remove(Medicine value) {
-    // TODO: implement remove
-    throw UnimplementedError();
+    return meds;
   }
 
+  /// Mark a medicine as deleted.
+  ///
+  /// Intakes will be deleted as soon as there is no [MedicineIntake]s
+  /// referencing them. They need to be stored to allow intakes of them to be
+  /// still displayed correctly.
+  Future<void> remove(Medicine value) => _db.update('Medicine', {
+      'removed': 1,
+    },
+    where: 'designation = ? AND color = ? AND defaultDose = ?',
+    whereArgs: [
+      value.designation,
+      value.color,
+      value.dosis,
+    ], // TODO: test for null values
+  );
+
+}
+
+extension _Castable on Object {
+  T? castOrNull<T>() {
+    if (this is T) return this as T;
+    return null;
+  }
 }
health_data_store/lib/src/types/medicine.dart
@@ -15,6 +15,6 @@ class Medicine with _$Medicine {
     /// Sample value: `0xFF42A5F5`
     int? color,
     /// Default dosis of medication.
-    int? dosis,
+    double? dosis,
   }) = _Medicine;
 }
health_data_store/lib/src/database_manager.dart
@@ -9,6 +9,7 @@ import 'package:sqflite_common/sqlite_api.dart';
 /// ## DB scheme
 ///
 /// ![Diagram](https://github.com/NobodyForNothing/blood-pressure-monitor-fl/assets/82763757/62edb58c-c579-4ce1-990c-be7889657fa7)
+/// // TODO: replace with updating file once merged
 ///
 /// ## Types
 /// Data in the database tries to always use the most common SI-units.
@@ -50,6 +51,8 @@ class DatabaseManager {
       '"medID"       INTEGER NOT NULL UNIQUE,'
       '"designation" TEXT NOT NULL,'
       '"defaultDose" REAL,'
+      '"color" INTEGER,'
+      '"removed" BOOLEAN,'
       'PRIMARY KEY("medID")'
     ');');
     await _db.execute('CREATE TABLE "Timestamps" ('
@@ -88,4 +91,7 @@ class DatabaseManager {
 
   /// Closes the database.
   Future<void> close() => _db.close();
+
+  // TODO: perform cleanup of medicines that are marked as deleted and have no
+  // intakes referencing them.
 }
health_data_store/test/src/repositories/medicine_repository_test.dart
@@ -0,0 +1,76 @@
+import 'package:health_data_store/health_data_store.dart';
+import 'package:test/expect.dart';
+import 'package:test/scaffolding.dart';
+
+import '../database_manager_test.dart';
+
+void main() {
+  sqfliteTestInit();
+  test('should initialize', () async {
+    final db = await mockDBManager();
+    addTearDown(db.close);
+    MedicineRepository(db.db);
+  });
+  test('should return no medicines when no are added', () async {
+    final db = await mockDBManager();
+    addTearDown(db.close);
+    final repo = MedicineRepository(db.db);
+    final all = await repo.getAll();
+    expect(all, isEmpty);
+  });
+  test('should store all complete medicines', () async {
+    final db = await mockDBManager();
+    addTearDown(db.close);
+    final repo = MedicineRepository(db.db);
+    await repo.add(Medicine(designation: 'med1', color: 0xFF226A, dosis: 42));
+    await repo.add(Medicine(designation: 'med2', color: 0xAF226B, dosis: 43));
+    final all = await repo.getAll();
+    expect(all, hasLength(2));
+    expect(all, containsAll([
+      isA<Medicine>()
+        .having((p0) => p0.designation, 'designation', 'med1')
+        .having((p0) => p0.color, 'color', 0xFF226A)
+        .having((p0) => p0.dosis, 'dosis', 42),
+      isA<Medicine>()
+        .having((p0) => p0.designation, 'designation', 'med2')
+        .having((p0) => p0.color, 'color', 0xAF226B)
+        .having((p0) => p0.dosis, 'dosis', 43),
+    ]));
+  });
+  test('should store all incomplete medicines', () async {
+    final db = await mockDBManager();
+    addTearDown(db.close);
+    final repo = MedicineRepository(db.db);
+    await repo.add(Medicine(designation: 'med1', color: 0xFF226A,));
+    await repo.add(Medicine(designation: 'med2', dosis: 43));
+    await repo.add(Medicine(designation: 'med3',));
+    final all = await repo.getAll();
+    expect(all, hasLength(3));
+    expect(all, containsAll([
+      isA<Medicine>()
+          .having((p0) => p0.designation, 'designation', 'med1')
+          .having((p0) => p0.color, 'color', 0xFF226A)
+          .having((p0) => p0.dosis, 'dosis', null),
+      isA<Medicine>()
+          .having((p0) => p0.designation, 'designation', 'med2')
+          .having((p0) => p0.color, 'color', null)
+          .having((p0) => p0.dosis, 'dosis', 43),
+      isA<Medicine>()
+          .having((p0) => p0.designation, 'designation', 'med3')
+          .having((p0) => p0.color, 'color', null)
+          .having((p0) => p0.dosis, 'dosis', null),
+    ]));
+  });
+  test('should mark medicines as deleted', () async {
+    final db = await mockDBManager();
+    addTearDown(db.close);
+    final repo = MedicineRepository(db.db);
+    final med1= Medicine(designation: 'med1', color: 0xFF226A, dosis: 42);
+    await repo.add(med1);
+    await repo.add(Medicine(designation: 'med2', color: 0xAF226B, dosis: 43));
+    expect(await repo.getAll(), hasLength(2));
+    await repo.remove(med1);
+    expect(await repo.getAll(), hasLength(1));
+  });
+
+}