chore: 批量代码优化与功能迭代更新
本次提交包含大量代码优化、功能新增与服务端配置更新: 1. 修复分析报告统计数据,调整CMake策略设置 2. 优化APP权限配置、编辑器与聊天界面组件 3. 更新依赖库版本与pubspec配置 4. 新增文件传输服务端、信令服务器相关配置与脚本 5. 完善用户注销功能与数据库迁移脚本 6. 优化多处动画效果、代码风格与日志输出 7. 新增多种调试与部署脚本,修复已知BUG
This commit is contained in:
@@ -1,14 +1,15 @@
|
||||
/// ============================================================
|
||||
/// 闲言APP — Drift 数据库定义
|
||||
/// 创建时间: 2026-04-20
|
||||
/// 更新时间: 2026-05-08
|
||||
/// 更新时间: 2026-05-12
|
||||
/// 作用: 本地 SQLite 数据库表结构定义 (Drift)
|
||||
/// 上次更新: 新增传输表TransferDevices/TransferRecords/PairingRecords/TransferMessages (v12)
|
||||
/// 上次更新: v13 新增6张表(云暂存/统计/剪贴板/画布/语音) + ALTER传输表断点续传/回执字段
|
||||
/// ============================================================
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:xianyan/core/utils/logger.dart';
|
||||
import 'database_connection/native.dart'
|
||||
if (dart.library.html) 'database_connection/web.dart';
|
||||
|
||||
@@ -401,6 +402,11 @@ class TransferMsgRecords extends Table {
|
||||
TextColumn get transferStatus => text().nullable()();
|
||||
TextColumn get deviceAlias => text().nullable()();
|
||||
TextColumn get deviceEmoji => text().nullable()();
|
||||
TextColumn get deliveryStatus => text().nullable()();
|
||||
DateTimeColumn get deliveredAt => dateTime().nullable()();
|
||||
DateTimeColumn get readAt => dateTime().nullable()();
|
||||
IntColumn get voiceDuration => integer().nullable()();
|
||||
TextColumn get voiceWaveform => text().nullable()();
|
||||
DateTimeColumn get timestamp => dateTime()();
|
||||
DateTimeColumn get createdAt => dateTime()();
|
||||
|
||||
@@ -408,6 +414,145 @@ class TransferMsgRecords extends Table {
|
||||
Set<Column> get primaryKey => {id};
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 云暂存记录表 — 文件传输助手
|
||||
// ============================================================
|
||||
|
||||
class CloudCacheRecords extends Table {
|
||||
TextColumn get id => text()();
|
||||
TextColumn get fileName => text()();
|
||||
IntColumn get fileSize => integer().withDefault(const Constant(0))();
|
||||
TextColumn get mimeType =>
|
||||
text().withDefault(const Constant('application/octet-stream'))();
|
||||
TextColumn get localPath => text().nullable()();
|
||||
TextColumn get cloudUrl => text().nullable()();
|
||||
TextColumn get encryptionKey => text().nullable()();
|
||||
TextColumn get iv => text().nullable()();
|
||||
TextColumn get uploadStatus =>
|
||||
text().withDefault(const Constant('pending'))();
|
||||
TextColumn get downloadStatus => text().withDefault(const Constant('none'))();
|
||||
DateTimeColumn get expiresAt => dateTime().nullable()();
|
||||
DateTimeColumn get uploadedAt => dateTime().nullable()();
|
||||
DateTimeColumn get downloadedAt => dateTime().nullable()();
|
||||
TextColumn get ownerId => text().withDefault(const Constant(''))();
|
||||
DateTimeColumn get createdAt => dateTime()();
|
||||
DateTimeColumn get updatedAt => dateTime()();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {id};
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 传输统计表 — 文件传输助手
|
||||
// ============================================================
|
||||
|
||||
class TransferStatsRecords extends Table {
|
||||
IntColumn get id => integer().autoIncrement()();
|
||||
TextColumn get date => text()();
|
||||
IntColumn get totalSentBytes => integer().withDefault(const Constant(0))();
|
||||
IntColumn get totalReceivedBytes =>
|
||||
integer().withDefault(const Constant(0))();
|
||||
IntColumn get fileCount => integer().withDefault(const Constant(0))();
|
||||
RealColumn get avgSpeed => real().withDefault(const Constant(0.0))();
|
||||
TextColumn get transportType => text().withDefault(const Constant('all'))();
|
||||
DateTimeColumn get createdAt => dateTime()();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 剪贴板记录表 — 文件传输助手
|
||||
// ============================================================
|
||||
|
||||
class ClipboardRecords extends Table {
|
||||
TextColumn get id => text()();
|
||||
TextColumn get content => text()();
|
||||
TextColumn get contentType => text().withDefault(const Constant('text'))();
|
||||
TextColumn get sourceDevice => text().withDefault(const Constant(''))();
|
||||
TextColumn get deviceId => text().withDefault(const Constant(''))();
|
||||
BoolColumn get isPinned => boolean().withDefault(const Constant(false))();
|
||||
DateTimeColumn get createdAt => dateTime()();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {id};
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 协作画布文档表 — 文件传输助手
|
||||
// ============================================================
|
||||
|
||||
class CanvasDocuments extends Table {
|
||||
TextColumn get id => text()();
|
||||
TextColumn get name => text()();
|
||||
TextColumn get ownerId => text().withDefault(const Constant(''))();
|
||||
IntColumn get width => integer().withDefault(const Constant(1920))();
|
||||
IntColumn get height => integer().withDefault(const Constant(1080))();
|
||||
TextColumn get backgroundJson => text().withDefault(const Constant('{}'))();
|
||||
DateTimeColumn get createdAt => dateTime()();
|
||||
DateTimeColumn get updatedAt => dateTime()();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {id};
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 协作画布笔画表 — 文件传输助手
|
||||
// ============================================================
|
||||
|
||||
class CanvasStrokes extends Table {
|
||||
TextColumn get id => text()();
|
||||
TextColumn get documentId => text()();
|
||||
TextColumn get userId => text()();
|
||||
TextColumn get pointsJson => text()();
|
||||
TextColumn get color => text().withDefault(const Constant('#000000'))();
|
||||
RealColumn get strokeWidth => real().withDefault(const Constant(2.0))();
|
||||
TextColumn get toolType => text().withDefault(const Constant('pen'))();
|
||||
DateTimeColumn get createdAt => dateTime()();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {id};
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 语音消息表 — 文件传输助手
|
||||
// ============================================================
|
||||
|
||||
class VoiceMessages extends Table {
|
||||
TextColumn get id => text()();
|
||||
TextColumn get messageId => text()();
|
||||
TextColumn get sessionId => text()();
|
||||
TextColumn get filePath => text()();
|
||||
IntColumn get duration => integer().withDefault(const Constant(0))();
|
||||
TextColumn get waveformJson => text().withDefault(const Constant('[]'))();
|
||||
BoolColumn get isRemote => boolean().withDefault(const Constant(false))();
|
||||
DateTimeColumn get createdAt => dateTime()();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {id};
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 迁移版本表 — 记录已执行的迁移步骤
|
||||
// ============================================================
|
||||
|
||||
class SchemaMigrations extends Table {
|
||||
IntColumn get version => integer()();
|
||||
DateTimeColumn get executedAt => dateTime()();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {version};
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 迁移错误日志表 — 记录迁移失败的详细信息
|
||||
// ============================================================
|
||||
|
||||
class MigrationErrors extends Table {
|
||||
IntColumn get id => integer().autoIncrement()();
|
||||
IntColumn get version => integer()();
|
||||
TextColumn get errorMessage => text()();
|
||||
TextColumn get errorStack => text()();
|
||||
DateTimeColumn get failedAt => dateTime()();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 数据库实例
|
||||
// ============================================================
|
||||
@@ -433,6 +578,14 @@ class TransferMsgRecords extends Table {
|
||||
TransferRecords,
|
||||
PairingRecords,
|
||||
TransferMsgRecords,
|
||||
CloudCacheRecords,
|
||||
TransferStatsRecords,
|
||||
ClipboardRecords,
|
||||
CanvasDocuments,
|
||||
CanvasStrokes,
|
||||
VoiceMessages,
|
||||
SchemaMigrations,
|
||||
MigrationErrors,
|
||||
],
|
||||
)
|
||||
class AppDatabase extends _$AppDatabase {
|
||||
@@ -442,86 +595,304 @@ class AppDatabase extends _$AppDatabase {
|
||||
static AppDatabase get instance => _instance;
|
||||
|
||||
@override
|
||||
int get schemaVersion => 12;
|
||||
int get schemaVersion => 13;
|
||||
|
||||
Future<bool> _isMigrationExecuted(int version) async {
|
||||
final result = await (select(
|
||||
schemaMigrations,
|
||||
)..where((t) => t.version.equals(version))).getSingleOrNull();
|
||||
return result != null;
|
||||
}
|
||||
|
||||
Future<void> _markMigrationExecuted(int version) async {
|
||||
await into(schemaMigrations).insert(
|
||||
SchemaMigrationsCompanion(
|
||||
version: Value(version),
|
||||
executedAt: Value(DateTime.now()),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _runMigration(
|
||||
int version,
|
||||
Future<void> Function() migration,
|
||||
) async {
|
||||
if (await _isMigrationExecuted(version)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await migration();
|
||||
await _markMigrationExecuted(version);
|
||||
} catch (e, stack) {
|
||||
await customStatement(
|
||||
'INSERT OR REPLACE INTO migration_errors (version, error_message, error_stack, failed_at) VALUES (?, ?, ?, ?)',
|
||||
[
|
||||
version,
|
||||
e.toString(),
|
||||
stack.toString(),
|
||||
DateTime.now().toIso8601String(),
|
||||
],
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _safeAddColumn(
|
||||
String table,
|
||||
String column,
|
||||
String definition,
|
||||
) async {
|
||||
final result = await customSelect(
|
||||
'SELECT COUNT(*) AS cnt FROM pragma_table_info(?) WHERE name = ?',
|
||||
variables: [Variable.withString(table), Variable.withString(column)],
|
||||
).getSingle();
|
||||
final exists = result.read<int>('cnt') > 0;
|
||||
if (!exists) {
|
||||
await customStatement('ALTER TABLE $table ADD COLUMN $definition');
|
||||
Log.i('DB Migration: Added column $column to $table');
|
||||
} else {
|
||||
Log.i('DB Migration: Column $column already exists in $table, skipping');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _migrateToV2(Migrator m) async {
|
||||
await m.createTable(toolUsageStats);
|
||||
}
|
||||
|
||||
Future<void> _migrateToV3(Migrator m) async {
|
||||
await m.createTable(feedCache);
|
||||
await m.createTable(offlineActionQueue);
|
||||
}
|
||||
|
||||
Future<void> _migrateToV4(Migrator m) async {
|
||||
await m.createTable(hanziCaches);
|
||||
}
|
||||
|
||||
Future<void> _migrateToV5(Migrator m) async {
|
||||
await m.addColumn(sentences, sentences.feedType);
|
||||
await m.addColumn(sentences, sentences.feedName);
|
||||
await m.addColumn(sentences, sentences.feedIcon);
|
||||
await m.addColumn(sentences, sentences.views);
|
||||
}
|
||||
|
||||
Future<void> _migrateToV6(Migrator m) async {
|
||||
await m.createTable(shareHistories);
|
||||
}
|
||||
|
||||
Future<void> _migrateToV7(Migrator m) async {
|
||||
await m.createTable(learningPlans);
|
||||
await m.createTable(learningRecords);
|
||||
}
|
||||
|
||||
Future<void> _migrateToV8(Migrator m) async {
|
||||
await _safeAddColumn(
|
||||
'sentences',
|
||||
'isLiked',
|
||||
'isLiked INTEGER NOT NULL DEFAULT 0',
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _migrateToV9(Migrator m) async {
|
||||
await m.createTable(chatConversations);
|
||||
await m.createTable(chatMsgRecords);
|
||||
await m.createTable(chatAttachments);
|
||||
await m.createTable(ipLocationCaches);
|
||||
await customStatement(
|
||||
'CREATE INDEX IF NOT EXISTS idx_chat_msg_records_conv ON chat_msg_records (conversation_id, timestamp DESC)',
|
||||
);
|
||||
await customStatement(
|
||||
'CREATE INDEX IF NOT EXISTS idx_chat_msg_records_conv_deleted ON chat_msg_records (conversation_id, is_deleted)',
|
||||
);
|
||||
await customStatement(
|
||||
'CREATE INDEX IF NOT EXISTS idx_chat_attachments_msg ON chat_attachments (message_id)',
|
||||
);
|
||||
await customStatement(
|
||||
'CREATE INDEX IF NOT EXISTS idx_chat_attachments_conv ON chat_attachments (conversation_id)',
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _migrateToV10(Migrator m) async {
|
||||
await _safeAddColumn(
|
||||
'chat_msg_records',
|
||||
'reply_to_id',
|
||||
'reply_to_id TEXT DEFAULT NULL',
|
||||
);
|
||||
await _safeAddColumn(
|
||||
'chat_msg_records',
|
||||
'rich_content',
|
||||
'rich_content TEXT NOT NULL DEFAULT \'\'',
|
||||
);
|
||||
await _safeAddColumn(
|
||||
'chat_msg_records',
|
||||
'ip_text',
|
||||
'ip_text TEXT NOT NULL DEFAULT \'\'',
|
||||
);
|
||||
await _safeAddColumn(
|
||||
'chat_msg_records',
|
||||
'ip_detail_json',
|
||||
'ip_detail_json TEXT NOT NULL DEFAULT \'\'',
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _migrateToV11(Migrator m) async {
|
||||
await _safeAddColumn(
|
||||
'chat_conversations',
|
||||
'is_hidden',
|
||||
'is_hidden INTEGER NOT NULL DEFAULT 0',
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _migrateToV12(Migrator m) async {
|
||||
await m.createTable(transferDeviceRecords);
|
||||
await m.createTable(transferRecords);
|
||||
await m.createTable(pairingRecords);
|
||||
await m.createTable(transferMsgRecords);
|
||||
}
|
||||
|
||||
Future<void> _migrateToV13(Migrator m) async {
|
||||
await m.createTable(cloudCacheRecords);
|
||||
await m.createTable(transferStatsRecords);
|
||||
await m.createTable(clipboardRecords);
|
||||
await m.createTable(canvasDocuments);
|
||||
await m.createTable(canvasStrokes);
|
||||
await m.createTable(voiceMessages);
|
||||
|
||||
await _safeAddColumn(
|
||||
'transfer_records',
|
||||
'file_id',
|
||||
'file_id TEXT DEFAULT NULL',
|
||||
);
|
||||
await _safeAddColumn(
|
||||
'transfer_records',
|
||||
'chunk_size',
|
||||
'chunk_size INTEGER NOT NULL DEFAULT 65536',
|
||||
);
|
||||
await _safeAddColumn(
|
||||
'transfer_records',
|
||||
'total_chunks',
|
||||
'total_chunks INTEGER DEFAULT NULL',
|
||||
);
|
||||
await _safeAddColumn(
|
||||
'transfer_records',
|
||||
'received_chunks',
|
||||
'received_chunks TEXT DEFAULT NULL',
|
||||
);
|
||||
await _safeAddColumn(
|
||||
'transfer_records',
|
||||
'retry_count',
|
||||
'retry_count INTEGER NOT NULL DEFAULT 0',
|
||||
);
|
||||
await _safeAddColumn(
|
||||
'transfer_records',
|
||||
'is_resumable',
|
||||
'is_resumable INTEGER NOT NULL DEFAULT 0',
|
||||
);
|
||||
await _safeAddColumn(
|
||||
'transfer_records',
|
||||
'paused_at',
|
||||
'paused_at TEXT DEFAULT NULL',
|
||||
);
|
||||
|
||||
await _safeAddColumn(
|
||||
'transfer_msg_records',
|
||||
'delivery_status',
|
||||
'delivery_status TEXT DEFAULT NULL',
|
||||
);
|
||||
await _safeAddColumn(
|
||||
'transfer_msg_records',
|
||||
'delivered_at',
|
||||
'delivered_at TEXT DEFAULT NULL',
|
||||
);
|
||||
await _safeAddColumn(
|
||||
'transfer_msg_records',
|
||||
'read_at',
|
||||
'read_at TEXT DEFAULT NULL',
|
||||
);
|
||||
await _safeAddColumn(
|
||||
'transfer_msg_records',
|
||||
'voice_duration',
|
||||
'voice_duration INTEGER DEFAULT NULL',
|
||||
);
|
||||
await _safeAddColumn(
|
||||
'transfer_msg_records',
|
||||
'voice_waveform',
|
||||
'voice_waveform TEXT DEFAULT NULL',
|
||||
);
|
||||
|
||||
await customStatement(
|
||||
'CREATE INDEX IF NOT EXISTS idx_cloud_cache_records_owner ON cloud_cache_records (owner_id)',
|
||||
);
|
||||
await customStatement(
|
||||
'CREATE INDEX IF NOT EXISTS idx_transfer_stats_records_date ON transfer_stats_records (date)',
|
||||
);
|
||||
await customStatement(
|
||||
'CREATE INDEX IF NOT EXISTS idx_clipboard_records_created ON clipboard_records (created_at DESC)',
|
||||
);
|
||||
await customStatement(
|
||||
'CREATE INDEX IF NOT EXISTS idx_canvas_strokes_document ON canvas_strokes (document_id)',
|
||||
);
|
||||
await customStatement(
|
||||
'CREATE INDEX IF NOT EXISTS idx_voice_messages_session ON voice_messages (session_id)',
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
MigrationStrategy get migration => MigrationStrategy(
|
||||
onCreate: (m) => m.createAll(),
|
||||
onUpgrade: (m, from, to) async {
|
||||
if (from < 2) {
|
||||
await m.createTable(toolUsageStats);
|
||||
}
|
||||
if (from < 3) {
|
||||
await m.createTable(feedCache);
|
||||
await m.createTable(offlineActionQueue);
|
||||
}
|
||||
if (from < 4) {
|
||||
await m.createTable(hanziCaches);
|
||||
}
|
||||
if (from < 5) {
|
||||
await m.addColumn(sentences, sentences.feedType);
|
||||
await m.addColumn(sentences, sentences.feedName);
|
||||
await m.addColumn(sentences, sentences.feedIcon);
|
||||
await m.addColumn(sentences, sentences.views);
|
||||
}
|
||||
if (from < 6) {
|
||||
await m.createTable(shareHistories);
|
||||
}
|
||||
if (from < 7) {
|
||||
await m.createTable(learningPlans);
|
||||
await m.createTable(learningRecords);
|
||||
}
|
||||
if (from < 8) {
|
||||
await customStatement(
|
||||
'ALTER TABLE sentences ADD COLUMN isLiked INTEGER NOT NULL DEFAULT 0',
|
||||
);
|
||||
}
|
||||
if (from < 9) {
|
||||
await m.createTable(chatConversations);
|
||||
await m.createTable(chatMsgRecords);
|
||||
await m.createTable(chatAttachments);
|
||||
await m.createTable(ipLocationCaches);
|
||||
await customStatement(
|
||||
'CREATE INDEX IF NOT EXISTS idx_chat_msg_records_conv ON chat_msg_records (conversation_id, timestamp DESC)',
|
||||
);
|
||||
await customStatement(
|
||||
'CREATE INDEX IF NOT EXISTS idx_chat_msg_records_conv_deleted ON chat_msg_records (conversation_id, is_deleted)',
|
||||
);
|
||||
await customStatement(
|
||||
'CREATE INDEX IF NOT EXISTS idx_chat_attachments_msg ON chat_attachments (message_id)',
|
||||
);
|
||||
await customStatement(
|
||||
'CREATE INDEX IF NOT EXISTS idx_chat_attachments_conv ON chat_attachments (conversation_id)',
|
||||
);
|
||||
}
|
||||
if (from < 10) {
|
||||
await customStatement(
|
||||
'ALTER TABLE chat_msg_records ADD COLUMN reply_to_id TEXT DEFAULT NULL',
|
||||
);
|
||||
await customStatement(
|
||||
'ALTER TABLE chat_msg_records ADD COLUMN rich_content TEXT NOT NULL DEFAULT \'\'',
|
||||
);
|
||||
await customStatement(
|
||||
'ALTER TABLE chat_msg_records ADD COLUMN ip_text TEXT NOT NULL DEFAULT \'\'',
|
||||
);
|
||||
await customStatement(
|
||||
'ALTER TABLE chat_msg_records ADD COLUMN ip_detail_json TEXT NOT NULL DEFAULT \'\'',
|
||||
);
|
||||
}
|
||||
if (from < 11) {
|
||||
await customStatement(
|
||||
'ALTER TABLE chat_conversations ADD COLUMN is_hidden INTEGER NOT NULL DEFAULT 0',
|
||||
);
|
||||
}
|
||||
if (from < 12) {
|
||||
await m.createTable(transferDeviceRecords);
|
||||
await m.createTable(transferRecords);
|
||||
await m.createTable(pairingRecords);
|
||||
await m.createTable(transferMsgRecords);
|
||||
onCreate: (m) async {
|
||||
await m.createAll();
|
||||
for (int v = 2; v <= schemaVersion; v++) {
|
||||
await _markMigrationExecuted(v);
|
||||
}
|
||||
},
|
||||
onUpgrade: (m, from, to) async {
|
||||
bool migrationTableExists = false;
|
||||
try {
|
||||
await customStatement('SELECT 1 FROM schema_migrations LIMIT 1');
|
||||
migrationTableExists = true;
|
||||
} catch (_) {
|
||||
migrationTableExists = false;
|
||||
}
|
||||
|
||||
if (!migrationTableExists) {
|
||||
await m.createTable(schemaMigrations);
|
||||
await m.createTable(migrationErrors);
|
||||
for (int v = 2; v <= from; v++) {
|
||||
await _markMigrationExecuted(v);
|
||||
}
|
||||
}
|
||||
|
||||
await _runMigration(2, () => _migrateToV2(m));
|
||||
await _runMigration(3, () => _migrateToV3(m));
|
||||
await _runMigration(4, () => _migrateToV4(m));
|
||||
await _runMigration(5, () => _migrateToV5(m));
|
||||
await _runMigration(6, () => _migrateToV6(m));
|
||||
await _runMigration(7, () => _migrateToV7(m));
|
||||
await _runMigration(8, () => _migrateToV8(m));
|
||||
await _runMigration(9, () => _migrateToV9(m));
|
||||
await _runMigration(10, () => _migrateToV10(m));
|
||||
await _runMigration(11, () => _migrateToV11(m));
|
||||
await _runMigration(12, () => _migrateToV12(m));
|
||||
await _runMigration(13, () => _migrateToV13(m));
|
||||
},
|
||||
);
|
||||
|
||||
Future<MigrationStatus> getMigrationStatus() async {
|
||||
final executedMigrations = await (select(
|
||||
schemaMigrations,
|
||||
)..orderBy([(t) => OrderingTerm.asc(t.version)])).get();
|
||||
|
||||
final failedMigrations = await (select(
|
||||
migrationErrors,
|
||||
)..orderBy([(t) => OrderingTerm.desc(t.failedAt)])).get();
|
||||
|
||||
return MigrationStatus(
|
||||
currentVersion: schemaVersion,
|
||||
executedVersions: executedMigrations.map((e) => e.version).toSet(),
|
||||
failedMigrations: failedMigrations,
|
||||
);
|
||||
}
|
||||
|
||||
// ---- 句子 CRUD ----
|
||||
|
||||
// ---- 分享历史 CRUD ----
|
||||
@@ -1456,3 +1827,20 @@ class HistorySentenceWithTime {
|
||||
final Sentence sentence;
|
||||
final DateTime readAt;
|
||||
}
|
||||
|
||||
class MigrationStatus {
|
||||
const MigrationStatus({
|
||||
required this.currentVersion,
|
||||
required this.executedVersions,
|
||||
required this.failedMigrations,
|
||||
});
|
||||
|
||||
final int currentVersion;
|
||||
final Set<int> executedVersions;
|
||||
final List<MigrationError> failedMigrations;
|
||||
|
||||
bool get hasFailedMigrations => failedMigrations.isNotEmpty;
|
||||
|
||||
bool get isFullyMigrated =>
|
||||
executedVersions.contains(currentVersion) && !hasFailedMigrations;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user