Обновлен проект. Добавлена БД
This commit is contained in:
BIN
lib/database/.DS_Store
vendored
Normal file
BIN
lib/database/.DS_Store
vendored
Normal file
Binary file not shown.
3
lib/database/connection/connection.dart
Normal file
3
lib/database/connection/connection.dart
Normal file
@@ -0,0 +1,3 @@
|
||||
// We use a conditional export to expose the right connection factory depending
|
||||
// on the platform.
|
||||
export 'native.dart';
|
||||
128
lib/database/connection/native.dart
Normal file
128
lib/database/connection/native.dart
Normal file
@@ -0,0 +1,128 @@
|
||||
// Dart imports:
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
|
||||
// Flutter imports:
|
||||
import 'package:flutter/foundation.dart' show kDebugMode;
|
||||
|
||||
// Package imports:
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:drift/isolate.dart';
|
||||
import 'package:drift/native.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:remever/inject.dart';
|
||||
import 'package:remever/services/core/enc_keys_service.dart';
|
||||
|
||||
/// Obtains a database connection for running drift in a Dart VM.
|
||||
///
|
||||
/// The [NativeDatabase] from drift will synchronously use sqlite3's C APIs.
|
||||
/// To move synchronous database work off the main thread, we use a
|
||||
/// [DriftIsolate], which can run queries in a background isolate under the
|
||||
/// hood.
|
||||
///
|
||||
DatabaseConnection connect() {
|
||||
return DatabaseConnection.delayed(
|
||||
Future<DatabaseConnection>.sync(() async {
|
||||
// Background isolates can't use platform channels, so let's use
|
||||
// `path_provider` in the main isolate and just send the result containing
|
||||
// the path over to the background isolate.
|
||||
final Directory appDir = await getApplicationDocumentsDirectory();
|
||||
final Directory dbDir = Directory(p.join(appDir.path, 'databases'));
|
||||
|
||||
if (!dbDir.existsSync()) {
|
||||
await dbDir.create(recursive: true);
|
||||
}
|
||||
|
||||
final String dbPath = p.join(appDir.path, 'databases', 'app.db.enc');
|
||||
|
||||
final EncKeysService encKeyService = getIt<EncKeysService>();
|
||||
final String key = await encKeyService.getRawKey('db');
|
||||
|
||||
final ReceivePort receiveDriftIsolate = ReceivePort();
|
||||
await Isolate.spawn(
|
||||
_entrypointForDriftIsolate,
|
||||
_IsolateStartRequest(receiveDriftIsolate.sendPort, dbPath, key),
|
||||
);
|
||||
|
||||
final DriftIsolate driftIsolate =
|
||||
await receiveDriftIsolate.first as DriftIsolate;
|
||||
|
||||
return driftIsolate.connect();
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/// The entrypoint of isolates can only take a single message, but we need two
|
||||
/// (a send port to reach the originating isolate and the database's path that
|
||||
/// should be opened on the background isolate). So, we bundle this information
|
||||
/// in a single class.
|
||||
class _IsolateStartRequest {
|
||||
final SendPort talkToMain;
|
||||
final String databasePath;
|
||||
final String key;
|
||||
|
||||
_IsolateStartRequest(this.talkToMain, this.databasePath, this.key);
|
||||
}
|
||||
|
||||
/// The entrypoint for a background isolate launching a drift server.
|
||||
///
|
||||
/// The main isolate can then connect to that isolate server to transparently
|
||||
/// run queries in the background.
|
||||
void _entrypointForDriftIsolate(_IsolateStartRequest request) {
|
||||
/// @see https://drift.simonbinder.eu/docs/other-engines/encryption/
|
||||
// open
|
||||
// ..overrideFor(OperatingSystem.android, openCipherOnAndroid)
|
||||
// ..overrideFor(
|
||||
// OperatingSystem.linux,
|
||||
// () => DynamicLibrary.open('libsqlcipher.so'),
|
||||
// )
|
||||
// ..overrideFor(
|
||||
// OperatingSystem.windows,
|
||||
// () => DynamicLibrary.open('sqlcipher.dll'),
|
||||
// );
|
||||
|
||||
if (kDebugMode) {
|
||||
print('[DRIFT] Path -> ${request.databasePath}');
|
||||
}
|
||||
|
||||
// The native database synchronously uses sqlite3's C API with `dart:ffi` for
|
||||
// a fast database implementation that doesn't require platform channels.
|
||||
final NativeDatabase databaseImpl = NativeDatabase(
|
||||
File(request.databasePath),
|
||||
logStatements: false,
|
||||
// setup: (Database db) {
|
||||
// // Check that we're actually running with SQLCipher by quering the
|
||||
// // cipher_version pragma.
|
||||
|
||||
// if (Platform.isAndroid) {
|
||||
// final ResultSet result = db.select('pragma cipher_version');
|
||||
|
||||
// if (result.isEmpty) {
|
||||
// throw UnsupportedError(
|
||||
// 'This database needs to run with SQLCipher, but that library is '
|
||||
// 'not available!',
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Then, apply the key to encrypt the database. Unfortunately, this
|
||||
// // pragma doesn't seem to support prepared statements so we inline the
|
||||
// // key.
|
||||
// final String escapedKey = request.key.replaceAll("'", "''");
|
||||
// db.execute("pragma key = '$escapedKey'");
|
||||
|
||||
// // Test that the key is correct by selecting from a table
|
||||
// db.execute('select count(*) from sqlite_master');
|
||||
// },
|
||||
);
|
||||
|
||||
// We can use DriftIsolate.inCurrent because this function is the entrypoint
|
||||
// of a background isolate itself.
|
||||
final DriftIsolate driftServer = DriftIsolate.inCurrent(
|
||||
() => DatabaseConnection(databaseImpl),
|
||||
);
|
||||
|
||||
// Inform the main isolate about the server we just created.
|
||||
request.talkToMain.send(driftServer);
|
||||
}
|
||||
22
lib/database/dao/collections_dao.dart
Normal file
22
lib/database/dao/collections_dao.dart
Normal file
@@ -0,0 +1,22 @@
|
||||
// Package imports:
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:remever/database/database.dart';
|
||||
import 'package:remever/database/tables.dart';
|
||||
|
||||
part 'collections_dao.g.dart';
|
||||
|
||||
@DriftAccessor(tables: <Type>[Collections])
|
||||
class CollectionsDao extends DatabaseAccessor<AppDatabase>
|
||||
with _$CollectionsDaoMixin {
|
||||
///
|
||||
/// Репозиторий для работы с коллекциями
|
||||
///
|
||||
CollectionsDao(super.attachedDatabase);
|
||||
|
||||
///
|
||||
/// Получение коллекций из базы данных
|
||||
///
|
||||
Stream<List<Collection>> getCollections() {
|
||||
return db.managers.collections.watch();
|
||||
}
|
||||
}
|
||||
8
lib/database/dao/collections_dao.g.dart
Normal file
8
lib/database/dao/collections_dao.g.dart
Normal file
@@ -0,0 +1,8 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'collections_dao.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
mixin _$CollectionsDaoMixin on DatabaseAccessor<AppDatabase> {
|
||||
$CollectionsTable get collections => attachedDatabase.collections;
|
||||
}
|
||||
63
lib/database/database.dart
Normal file
63
lib/database/database.dart
Normal file
@@ -0,0 +1,63 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:remever/database/dao/collections_dao.dart';
|
||||
import 'package:remever/database/tables.dart';
|
||||
import 'connection/connection.dart' as impl;
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
part 'database.g.dart';
|
||||
|
||||
@DriftDatabase(
|
||||
include: <String>{'sql.drift'},
|
||||
daos: <Type>[CollectionsDao],
|
||||
tables: <Type>[Collections],
|
||||
)
|
||||
@Singleton()
|
||||
final class AppDatabase extends _$AppDatabase {
|
||||
AppDatabase() : super(impl.connect());
|
||||
|
||||
AppDatabase.fromExcecutor(super.executor);
|
||||
|
||||
@override
|
||||
int get schemaVersion => 1;
|
||||
|
||||
@override
|
||||
MigrationStrategy get migration {
|
||||
return MigrationStrategy(
|
||||
beforeOpen: (OpeningDetails details) async {
|
||||
await enableFK();
|
||||
// await customStatement('PRAGMA journal_mode = WAL');
|
||||
},
|
||||
onCreate: (Migrator migrator) async {
|
||||
await migrator.createAll();
|
||||
},
|
||||
onUpgrade: _onUpgrade,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> enableFK() => customStatement('PRAGMA foreign_keys = ON');
|
||||
Future<void> disableFK() => customStatement('PRAGMA foreign_keys = OFF');
|
||||
|
||||
// ignore: long-method
|
||||
Future<void> _onUpgrade(Migrator m, int from, int to) async {
|
||||
await customStatement('PRAGMA foreign_keys = OFF');
|
||||
|
||||
for (int step = from + 1; step <= to; step++) {
|
||||
switch (step) {}
|
||||
}
|
||||
|
||||
// Assert that the schema is valid after migrations
|
||||
if (kDebugMode) {
|
||||
final List<QueryRow> wrongForeignKeys =
|
||||
await customSelect('PRAGMA foreign_key_check').get();
|
||||
|
||||
assert(
|
||||
wrongForeignKeys.isEmpty,
|
||||
'${wrongForeignKeys.map((QueryRow e) => e.data)}',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// --
|
||||
}
|
||||
1035
lib/database/database.g.dart
Normal file
1035
lib/database/database.g.dart
Normal file
File diff suppressed because it is too large
Load Diff
1
lib/database/sql.drift
Normal file
1
lib/database/sql.drift
Normal file
@@ -0,0 +1 @@
|
||||
import 'tables.dart';
|
||||
77
lib/database/tables.dart
Normal file
77
lib/database/tables.dart
Normal file
@@ -0,0 +1,77 @@
|
||||
// ignore_for_file: recursive_getters
|
||||
|
||||
// Dart imports:
|
||||
import 'dart:convert';
|
||||
import 'dart:ffi';
|
||||
|
||||
// Package imports:
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
///
|
||||
/// Примесь для добавления основного ключа в виде текста
|
||||
/// Для хранения uuid
|
||||
///
|
||||
mixin _UuidPrimaryKey on Table {
|
||||
@override
|
||||
// ignore: always_specify_types
|
||||
Set<Column>? get primaryKey => <TextColumn>{id};
|
||||
|
||||
/// Идентификатор записи
|
||||
TextColumn get id =>
|
||||
text()
|
||||
.withLength(min: 36, max: 36)
|
||||
.clientDefault(() => const Uuid().v6())();
|
||||
}
|
||||
|
||||
///
|
||||
/// Примесь для добавления полей даты
|
||||
///
|
||||
mixin _Timestampable on Table {
|
||||
/// Дата создания
|
||||
DateTimeColumn get createdAt =>
|
||||
dateTime()
|
||||
.named('created_at')
|
||||
.check(createdAt.isBiggerThan(Constant<DateTime>(DateTime(1950))))
|
||||
.withDefault(currentDateAndTime)();
|
||||
|
||||
/// Дата последней модификации
|
||||
DateTimeColumn get updatedAt =>
|
||||
dateTime()
|
||||
.named('updated_at')
|
||||
.check(updatedAt.isBiggerThan(Constant<DateTime>(DateTime(1950))))
|
||||
.withDefault(currentDateAndTime)();
|
||||
}
|
||||
|
||||
///
|
||||
/// Примесь для добавления полей даты удаления
|
||||
///
|
||||
mixin _Deletable on Table {
|
||||
/// Дата удаления
|
||||
DateTimeColumn get deletedAt =>
|
||||
dateTime()
|
||||
.named('deleted_at')
|
||||
.nullable()
|
||||
.check(deletedAt.isBiggerThan(Constant<DateTime>(DateTime(1950))))();
|
||||
}
|
||||
|
||||
/// -- Таблицы --
|
||||
|
||||
///
|
||||
///
|
||||
///
|
||||
@DataClassName('Collection')
|
||||
class Collections extends Table with _UuidPrimaryKey, _Timestampable {
|
||||
TextColumn get title => text()();
|
||||
|
||||
TextColumn get desc => text()();
|
||||
|
||||
TextColumn get image => text().nullable()();
|
||||
|
||||
TextColumn get payload => text().nullable()();
|
||||
|
||||
IntColumn get likesCount => integer().withDefault(Constant(0))();
|
||||
BoolColumn get isLiked => boolean().withDefault(Constant(false))();
|
||||
BoolColumn get isPublic => boolean().withDefault(Constant(false))();
|
||||
BoolColumn get includeInTraining => boolean().withDefault(Constant(false))();
|
||||
}
|
||||
Reference in New Issue
Block a user