131 lines
3.7 KiB
Dart
131 lines
3.7 KiB
Dart
import 'dart:convert';
|
|
|
|
import 'package:archive/archive.dart';
|
|
import 'package:miaoji_zhang/shared/services/local_database.dart';
|
|
import 'package:miaoji_zhang/shared/services/session_store.dart';
|
|
|
|
class LocalExportService {
|
|
const LocalExportService._();
|
|
|
|
static List<int> buildZip() {
|
|
final snapshot = LocalDatabase.instance.exportSnapshot();
|
|
final session = SessionStore.instance;
|
|
return buildZipFromSnapshot(
|
|
snapshot,
|
|
profile: {
|
|
'mode': session.isGuest ? 'guest' : 'account',
|
|
'userId': session.userId,
|
|
'username': session.username,
|
|
'nickname': session.nickname,
|
|
},
|
|
);
|
|
}
|
|
|
|
static List<int> buildZipFromSnapshot(
|
|
Map<String, dynamic> snapshot, {
|
|
Map<String, dynamic> profile = const {},
|
|
}) {
|
|
final document = <String, dynamic>{...snapshot, 'profile': profile};
|
|
final ledgers = _indexById(snapshot['ledgers'] as List<dynamic>);
|
|
final categories = _indexById(snapshot['categories'] as List<dynamic>);
|
|
final transactions = (snapshot['transactions'] as List<dynamic>)
|
|
.cast<Map<String, dynamic>>();
|
|
final budgets = (snapshot['budgets'] as List<dynamic>)
|
|
.cast<Map<String, dynamic>>();
|
|
|
|
final archive = Archive();
|
|
archive.addFile(
|
|
ArchiveFile.bytes(
|
|
'transactions.csv',
|
|
_utf8Bom(
|
|
_csv([
|
|
const [
|
|
'id',
|
|
'ledger',
|
|
'type',
|
|
'amount',
|
|
'category',
|
|
'note',
|
|
'payment_method',
|
|
'source',
|
|
'occurred_at',
|
|
'is_deleted',
|
|
'updated_at',
|
|
],
|
|
for (final item in transactions)
|
|
[
|
|
item['id'],
|
|
ledgers[item['ledgerId']]?['name'],
|
|
item['type'],
|
|
(item['amount'] as num).toStringAsFixed(2),
|
|
item['categoryName'],
|
|
item['note'],
|
|
item['paymentMethod'],
|
|
item['source'],
|
|
item['occurredAt'],
|
|
item['isDeleted'],
|
|
item['updatedAt'],
|
|
],
|
|
]),
|
|
),
|
|
),
|
|
);
|
|
archive.addFile(
|
|
ArchiveFile.bytes(
|
|
'budgets.csv',
|
|
_utf8Bom(
|
|
_csv([
|
|
const [
|
|
'ledger',
|
|
'period',
|
|
'category',
|
|
'amount',
|
|
'recurring',
|
|
'updated_at',
|
|
],
|
|
for (final item in budgets)
|
|
[
|
|
ledgers[item['ledgerId']]?['name'],
|
|
item['period'],
|
|
item['categoryId'] == null
|
|
? 'total'
|
|
: categories[item['categoryId']]?['name'],
|
|
(item['amount'] as num).toStringAsFixed(2),
|
|
item['recurring'],
|
|
item['updatedAt'],
|
|
],
|
|
]),
|
|
),
|
|
),
|
|
);
|
|
archive.addFile(
|
|
ArchiveFile.string(
|
|
'jizhi-backup.json',
|
|
const JsonEncoder.withIndent(' ').convert(document),
|
|
),
|
|
);
|
|
return ZipEncoder().encode(archive);
|
|
}
|
|
|
|
static Map<int, Map<String, dynamic>> _indexById(List<dynamic> items) => {
|
|
for (final item in items.cast<Map<String, dynamic>>())
|
|
item['id'] as int: item,
|
|
};
|
|
|
|
static List<int> _utf8Bom(String value) => [
|
|
0xef,
|
|
0xbb,
|
|
0xbf,
|
|
...utf8.encode(value),
|
|
];
|
|
|
|
static String _csv(List<List<Object?>> rows) =>
|
|
rows.map((row) => row.map(_csvCell).join(',')).join('\r\n');
|
|
|
|
static String _csvCell(Object? value) {
|
|
final text = value?.toString() ?? '';
|
|
if (!text.contains(RegExp('[,"\r\n]'))) return text;
|
|
return '"${text.replaceAll('"', '""')}"';
|
|
}
|
|
}
|