MotorSignal-App/lib/services/cloud/account_cloud_cleanup_servi...

80 lines
3.0 KiB
Dart

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:http/http.dart' as http;
import '../auth/auth_service.dart';
import '../sync/r2_upload_service.dart';
abstract class CloudAccountCleanup {
Future<void> deleteCloudData();
}
class AccountCloudCleanupService implements CloudAccountCleanup {
final AuthService _auth;
final FirebaseFirestore? _firestore;
final http.Client _client;
final String _workerUrl;
AccountCloudCleanupService({
required this._auth,
FirebaseFirestore? firestore,
http.Client? client,
String? workerUrl,
}) : _firestore = firestore ?? _resolveFirestore(),
_client = client ?? http.Client(),
_workerUrl = (workerUrl ?? R2UploadService.defaultWorkerUrl).replaceAll(RegExp(r'/+$'), '');
static FirebaseFirestore? _resolveFirestore() {
try {
if (Firebase.apps.isNotEmpty) return FirebaseFirestore.instance;
} catch (_) {}
return null;
}
@override
Future<void> deleteCloudData() async {
final user = _auth.currentUser;
if (user == null || user.isAnonymous) throw StateError('A permanent account is required.');
final token = await _auth.getIdToken(true);
if (token == null || token.isEmpty) throw StateError('A valid account session is required.');
final response = await _client.delete(
Uri.parse('$_workerUrl/account/cloud-data'),
headers: {'Authorization': 'Bearer ${token.trim()}'},
).timeout(const Duration(seconds: 30));
if (response.statusCode < 200 || response.statusCode >= 300) {
throw StateError('MotorCloud cleanup was not completed (${response.statusCode}).');
}
final firestore = _firestore;
if (firestore == null) throw StateError('MotorCloud database is unavailable.');
final userRef = firestore.collection('users').doc(user.uid);
final rides = await userRef.collection('rides').get();
for (final doc in rides.docs) {
await _deleteCollection(doc.reference.collection('events'));
await _deleteCollection(doc.reference.collection('performance'));
}
await _deleteCollection(userRef.collection('rides'));
await _deleteCollection(userRef.collection('motorcycles'));
await _deleteCollection(userRef.collection('trips'));
await _deleteCollection(userRef.collection('events'));
await _deleteCollection(userRef.collection('friends'));
// Delete the profile last so retries can still discover the account scope.
final profileBatch = firestore.batch()..delete(userRef);
await profileBatch.commit();
}
Future<void> _deleteCollection(CollectionReference<Map<String, dynamic>> collection) async {
final snapshot = await collection.get();
for (var offset = 0; offset < snapshot.docs.length; offset += 450) {
final batch = _firestore!.batch();
final end = (offset + 450).clamp(0, snapshot.docs.length);
for (final doc in snapshot.docs.sublist(offset, end)) {
batch.delete(doc.reference);
}
await batch.commit();
}
}
}