Обновлен проект. Добавлена БД

This commit is contained in:
2025-03-03 23:57:55 +03:00
parent 273e68557a
commit cb6ce05059
726 changed files with 9424 additions and 478 deletions

BIN
lib/database/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,3 @@
// We use a conditional export to expose the right connection factory depending
// on the platform.
export 'native.dart';

View 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);
}

View 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();
}
}

View 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;
}

View 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

File diff suppressed because it is too large Load Diff

1
lib/database/sql.drift Normal file
View File

@@ -0,0 +1 @@
import 'tables.dart';

77
lib/database/tables.dart Normal file
View 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))();
}