fix: route History upload through authenticated MotorCloud and scope sync state per user
This commit is contained in:
parent
ed269a0e67
commit
272a2b56ee
|
|
@ -34,16 +34,16 @@ class GitHubUploadResult {
|
|||
}) : timestamp = timestamp ?? DateTime.now();
|
||||
}
|
||||
|
||||
/// Ride synchronization service.
|
||||
/// Compatibility facade for the historical ride-detail sync service.
|
||||
///
|
||||
/// Historical versions of the ride-detail screen called this service directly,
|
||||
/// which required a separate GitHub PAT even when the user was already signed
|
||||
/// into MotorCloud with Google/Firebase. When no GitHub PAT is configured we now
|
||||
/// use the authoritative Firebase + R2 provider, matching Settings > Back Up All.
|
||||
/// GitHub remains available when a PAT is explicitly configured.
|
||||
/// MotorSignal cloud backup is Firebase Authentication + Firestore + R2.
|
||||
/// The old GitHub PAT exporter remains available only through the explicit
|
||||
/// `uploadRideToGitHub` developer/export path and is never used by the normal
|
||||
/// History -> Upload Now action.
|
||||
class GitHubSyncService extends ChangeNotifier {
|
||||
final AppDatabase _db;
|
||||
final GitHubAuthService _auth;
|
||||
final GitHubAuthService _githubAuth;
|
||||
final AuthService _authService;
|
||||
final SharedPreferences _prefs;
|
||||
final http.Client _client;
|
||||
|
||||
|
|
@ -51,16 +51,40 @@ class GitHubSyncService extends ChangeNotifier {
|
|||
String _statusMessage = '';
|
||||
final Set<int> _uploadQueue = {};
|
||||
|
||||
GitHubSyncService(this._db, this._auth, this._prefs, [http.Client? client])
|
||||
: _client = client ?? http.Client();
|
||||
GitHubSyncService(
|
||||
this._db,
|
||||
this._githubAuth,
|
||||
this._authService,
|
||||
this._prefs, [http.Client? client],
|
||||
) : _client = client ?? http.Client();
|
||||
|
||||
GitHubUploadState get state => _state;
|
||||
String get statusMessage => _statusMessage;
|
||||
Set<int> get queuedRideIds => Set.unmodifiable(_uploadQueue);
|
||||
|
||||
bool isRideUploaded(int rideId) => _prefs.getBool('github_uploaded_ride_$rideId') ?? false;
|
||||
String? getRideCommitSha(int rideId) => _prefs.getString('github_sha_ride_$rideId');
|
||||
String? getRideUploadTimestamp(int rideId) => _prefs.getString('github_uploaded_at_ride_$rideId');
|
||||
String? _userScopedKey(String key, int rideId) {
|
||||
final uid = _authService.currentUser?.uid;
|
||||
if (uid == null || uid.isEmpty) return null;
|
||||
return '${key}_${uid}_$rideId';
|
||||
}
|
||||
|
||||
bool isRideUploaded(int rideId) {
|
||||
final scoped = _userScopedKey('motorcloud_uploaded_ride', rideId);
|
||||
if (scoped == null) return false;
|
||||
return _prefs.getBool(scoped) ?? false;
|
||||
}
|
||||
|
||||
String? getRideCommitSha(int rideId) {
|
||||
final scoped = _userScopedKey('motorcloud_ride_remote_id', rideId);
|
||||
if (scoped == null) return null;
|
||||
return _prefs.getString(scoped);
|
||||
}
|
||||
|
||||
String? getRideUploadTimestamp(int rideId) {
|
||||
final scoped = _userScopedKey('motorcloud_uploaded_at_ride', rideId);
|
||||
if (scoped == null) return null;
|
||||
return _prefs.getString(scoped);
|
||||
}
|
||||
|
||||
void queueRideForUpload(int rideId) {
|
||||
if (isRideUploaded(rideId)) return;
|
||||
|
|
@ -79,19 +103,43 @@ class GitHubSyncService extends ChangeNotifier {
|
|||
}
|
||||
}
|
||||
|
||||
/// Normal History -> Upload Now operation.
|
||||
///
|
||||
/// This deliberately ignores any configured GitHub PAT. A user's ride must
|
||||
/// be written to that user's MotorCloud account, not to a public developer
|
||||
/// repository. Authentication and ownership are enforced by Firebase/R2.
|
||||
Future<GitHubUploadResult> uploadRide(int rideId, {bool isManual = false}) async {
|
||||
// IMPORTANT: Google/Firebase authentication is the normal MotorCloud cloud
|
||||
// authentication. A GitHub PAT is optional and only needed for explicit
|
||||
// GitHub repository export.
|
||||
final githubToken = _auth.getToken();
|
||||
if (githubToken == null || githubToken.isEmpty) {
|
||||
return _uploadToMotorCloud(rideId);
|
||||
}
|
||||
|
||||
/// Explicit legacy/developer export. Not used by the normal ride UI.
|
||||
Future<GitHubUploadResult> uploadRideToGitHub(int rideId) async {
|
||||
final githubToken = _githubAuth.getToken();
|
||||
if (githubToken == null || githubToken.isEmpty) {
|
||||
return GitHubUploadResult(
|
||||
success: false,
|
||||
message: 'GitHub export requires an explicitly configured GitHub token.',
|
||||
);
|
||||
}
|
||||
return _uploadToGitHub(rideId, githubToken);
|
||||
}
|
||||
|
||||
Future<GitHubUploadResult> _uploadToMotorCloud(int rideId) async {
|
||||
final user = _authService.currentUser;
|
||||
if (user == null || user.isAnonymous) {
|
||||
_state = GitHubUploadState.authRequired;
|
||||
_statusMessage = 'Sign in to MotorCloud before uploading a ride.';
|
||||
notifyListeners();
|
||||
return GitHubUploadResult(success: false, message: _statusMessage);
|
||||
}
|
||||
|
||||
if (isRideUploaded(rideId)) {
|
||||
_state = GitHubUploadState.success;
|
||||
_statusMessage = 'Ride #$rideId is already backed up to your MotorCloud account.';
|
||||
notifyListeners();
|
||||
return GitHubUploadResult(success: true, message: _statusMessage, commitSha: getRideCommitSha(rideId), targetPath: 'MotorCloud');
|
||||
}
|
||||
|
||||
_state = GitHubUploadState.uploading;
|
||||
_statusMessage = 'Backing up Ride #$rideId to MotorCloud...';
|
||||
notifyListeners();
|
||||
|
|
@ -99,7 +147,7 @@ class GitHubSyncService extends ChangeNotifier {
|
|||
try {
|
||||
final provider = FirebaseSyncProvider(
|
||||
db: _db,
|
||||
authService: FirebaseAuthService(),
|
||||
authService: _authService,
|
||||
);
|
||||
final result = await provider.uploadRide(
|
||||
SyncItem(
|
||||
|
|
@ -110,14 +158,15 @@ class GitHubSyncService extends ChangeNotifier {
|
|||
);
|
||||
|
||||
if (result.success) {
|
||||
await _prefs.setBool('github_uploaded_ride_$rideId', true);
|
||||
await _prefs.setString('github_uploaded_at_ride_$rideId', DateTime.now().toIso8601String());
|
||||
if (result.remoteId != null) {
|
||||
await _prefs.setString('github_sha_ride_$rideId', result.remoteId!);
|
||||
}
|
||||
final uploadedKey = _userScopedKey('motorcloud_uploaded_ride', rideId)!;
|
||||
final timestampKey = _userScopedKey('motorcloud_uploaded_at_ride', rideId)!;
|
||||
final remoteIdKey = _userScopedKey('motorcloud_ride_remote_id', rideId)!;
|
||||
await _prefs.setBool(uploadedKey, true);
|
||||
await _prefs.setString(timestampKey, DateTime.now().toIso8601String());
|
||||
if (result.remoteId != null) await _prefs.setString(remoteIdKey, result.remoteId!);
|
||||
_uploadQueue.remove(rideId);
|
||||
_state = GitHubUploadState.success;
|
||||
_statusMessage = 'Ride #$rideId backed up to MotorCloud';
|
||||
_statusMessage = 'Ride #$rideId backed up to your MotorCloud account';
|
||||
notifyListeners();
|
||||
return GitHubUploadResult(
|
||||
success: true,
|
||||
|
|
@ -127,11 +176,7 @@ class GitHubSyncService extends ChangeNotifier {
|
|||
);
|
||||
}
|
||||
|
||||
if (result.requiresAuth) {
|
||||
_state = GitHubUploadState.authRequired;
|
||||
} else {
|
||||
_state = GitHubUploadState.failed;
|
||||
}
|
||||
_state = result.requiresAuth ? GitHubUploadState.authRequired : GitHubUploadState.failed;
|
||||
_statusMessage = result.errorMessage ?? 'MotorCloud backup failed';
|
||||
notifyListeners();
|
||||
return GitHubUploadResult(success: false, message: _statusMessage);
|
||||
|
|
@ -154,8 +199,8 @@ class GitHubSyncService extends ChangeNotifier {
|
|||
|
||||
final dateStr = DateFormat('yyyy-MM-dd-HHmmss').format(ride.startTime.toLocal());
|
||||
final folderName = 'field_test_session_${rideId}_$dateStr/field_test_session';
|
||||
final repo = _auth.getRepository();
|
||||
final branch = _auth.getBranch();
|
||||
final repo = _githubAuth.getRepository();
|
||||
final branch = _githubAuth.getBranch();
|
||||
final fieldTestExport = FieldTestExportService(_db);
|
||||
final exportService = ExportService(_db);
|
||||
final sessionJson = await fieldTestExport.generateFieldTestSessionJson(rideId);
|
||||
|
|
@ -262,17 +307,13 @@ class GitHubSyncService extends ChangeNotifier {
|
|||
}
|
||||
}
|
||||
|
||||
await _prefs.setBool('github_uploaded_ride_$rideId', true);
|
||||
await _prefs.setString('github_uploaded_at_ride_$rideId', DateTime.now().toIso8601String());
|
||||
if (lastCommitSha != null) await _prefs.setString('github_sha_ride_$rideId', lastCommitSha);
|
||||
_uploadQueue.remove(rideId);
|
||||
_state = GitHubUploadState.success;
|
||||
_statusMessage = 'Ride #$rideId successfully uploaded to $repo ($branch)';
|
||||
_statusMessage = 'Ride #$rideId exported to $repo ($branch)';
|
||||
notifyListeners();
|
||||
return GitHubUploadResult(success: true, message: _statusMessage, commitSha: lastCommitSha, targetPath: folderName);
|
||||
} catch (e) {
|
||||
_state = GitHubUploadState.failed;
|
||||
_statusMessage = 'Upload failed for Ride #$rideId: $e';
|
||||
_statusMessage = 'GitHub export failed for Ride #$rideId: $e';
|
||||
notifyListeners();
|
||||
return GitHubUploadResult(success: false, message: _statusMessage);
|
||||
}
|
||||
|
|
@ -281,7 +322,8 @@ class GitHubSyncService extends ChangeNotifier {
|
|||
|
||||
final githubSyncServiceProvider = ChangeNotifierProvider<GitHubSyncService>((ref) {
|
||||
final db = ref.watch(databaseProvider);
|
||||
final auth = ref.watch(githubAuthServiceProvider);
|
||||
final githubAuth = ref.watch(githubAuthServiceProvider);
|
||||
final authService = ref.watch(authServiceProvider);
|
||||
final prefs = ref.watch(sharedPreferencesProvider);
|
||||
return GitHubSyncService(db, auth, prefs);
|
||||
return GitHubSyncService(db, githubAuth, authService, prefs);
|
||||
});
|
||||
Loading…
Reference in New Issue