replaced account data singleton with a session model

This commit is contained in:
2026-09-27 14:49:38 +02:00
parent 79b33ab248
commit 5e65c4671a
60 changed files with 1019 additions and 679 deletions
@@ -29,17 +29,12 @@ class AuthVerify extends MarianumConnectQuery {
required String username,
required String password,
}) async {
final token = await _tokenStorage.readToken();
if (token == null || token.isEmpty) {
throw AuthException.unauthorized(
technicalDetails: 'AuthVerify: no bearer token in storage',
);
}
final options = await _tokenStorage.requireBearerOptions('AuthVerify');
return guard(() async {
await dio.post<void>(
endpoint('auth/verify'),
data: {'username': username, 'password': password},
options: Options(headers: {'Authorization': 'Bearer $token'}),
options: options,
);
});
}
@@ -1,7 +1,5 @@
import 'dart:typed_data';
import 'package:dio/dio.dart';
import '../../marianumconnect_query.dart';
/// Downloads the raw PDF bytes of a Marianum Message from
@@ -15,11 +13,6 @@ class GetNewsletterFile extends MarianumConnectQuery {
GetNewsletterFile(this.id, {super.dio});
Future<Uint8List> run() => guard(() async {
final response = await dio.get<List<int>>(
endpoint('newsletter/${Uri.encodeComponent(id)}/file'),
options: Options(responseType: ResponseType.bytes),
);
return Uint8List.fromList(response.data!);
});
Future<Uint8List> run() =>
getBytes('newsletter/${Uri.encodeComponent(id)}/file');
}
@@ -1,7 +1,5 @@
import 'dart:typed_data';
import 'package:dio/dio.dart';
import '../../marianumconnect_query.dart';
/// Downloads the raw bytes of a PROXIED_FILE ticker page from
@@ -15,11 +13,6 @@ class GetTickerPageFile extends MarianumConnectQuery {
GetTickerPageFile(this.slug, {super.dio});
Future<Uint8List> run() => guard(() async {
final response = await dio.get<List<int>>(
endpoint('ticker/pages/${Uri.encodeComponent(slug)}/file'),
options: Options(responseType: ResponseType.bytes),
);
return Uint8List.fromList(response.data!);
});
Future<Uint8List> run() =>
getBytes('ticker/pages/${Uri.encodeComponent(slug)}/file');
}
@@ -1,7 +1,7 @@
import 'dart:math';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import '../../../../utils/random_id.dart';
/// A stable, anonymous per-install identifier for telemetry. Generated once on
/// first use (128 bits from a cryptographic RNG) and persisted in the secure
/// keystore, so a device stays a single row across password rotations and FCM
@@ -21,15 +21,9 @@ class TelemetryDeviceId {
_cached = existing;
return existing;
}
final generated = _generate();
final generated = randomHexId();
await _storage.write(key: _key, value: generated);
_cached = generated;
return generated;
}
static String _generate() {
final random = Random.secure();
final bytes = List<int>.generate(16, (_) => random.nextInt(256));
return bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join();
}
}
@@ -2,8 +2,8 @@ import 'dart:developer';
import 'package:shared_preferences/shared_preferences.dart';
import '../../../../model/account_data.dart';
import '../../../demo/demo_mode.dart';
import '../../../../session/session.dart';
import '../../../../session/session_manager.dart';
import '../../../mhsl/custom_timetable_event/get/get_custom_timetable_event.dart';
import '../../../mhsl/custom_timetable_event/get/get_custom_timetable_event_params.dart';
import '../../../mhsl/custom_timetable_event/remove/remove_custom_timetable_event.dart';
@@ -26,28 +26,32 @@ class CustomEventsMigration {
const CustomEventsMigration._();
static Future<void> runOnce() async {
if (DemoMode.active) return;
// Only password accounts can derive the legacy identity.
final session = SessionManager().current;
if (session is! CredentialSession || session.isDemo) return;
if (await _isDone()) return;
final epoch = AccountData().sessionEpoch;
final epoch = SessionManager().sessionEpoch;
try {
final response = await GetCustomTimetableEvent(
GetCustomTimetableEventParams(AccountData().getUserSecret()),
GetCustomTimetableEventParams(session.legacyUserSecret),
).run();
for (final event in response.events) {
// The POST authenticates with whoever is signed in now; after a
// sign-out the previous account's events would land in the next one.
if (!AccountData().isCurrentSession(epoch)) return;
if (!SessionManager().isCurrentSession(epoch)) return;
await TimetableCustomEventsAdd().run(event);
await RemoveCustomTimetableEvent(
RemoveCustomTimetableEventParams(event.id),
).run();
}
if (!AccountData().isCurrentSession(epoch)) return;
if (!SessionManager().isCurrentSession(epoch)) return;
await _markDone();
log('Custom events migration: moved ${response.events.length} event(s) to Marianum-Connect.');
log(
'Custom events migration: moved ${response.events.length} event(s) to Marianum-Connect.',
);
} catch (e) {
// Leave the flag unset so the next launch retries; the delete-after-post
// above keeps a partial run duplicate-free.