fix: bind ride backup to shared Firebase identity and prevent GitHub routing

This commit is contained in:
bponders12-cell 2026-09-01 20:41:29 -04:00
parent 272a2b56ee
commit f31e0b8eaa
1 changed files with 49 additions and 42 deletions

View File

@ -36,10 +36,10 @@ class GitHubUploadResult {
/// Compatibility facade for the historical ride-detail sync service.
///
/// 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.
/// Normal MotorSignal cloud backup is Firebase Authentication + Firestore + R2.
/// The historical 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 _githubAuth;
@ -51,12 +51,17 @@ class GitHubSyncService extends ChangeNotifier {
String _statusMessage = '';
final Set<int> _uploadQueue = {};
/// Keeps the historical constructor compatible with tests/callers that pass
/// an HTTP client as the optional fourth argument. Production additionally
/// supplies the shared Firebase AuthService as the fifth argument.
GitHubSyncService(
this._db,
this._githubAuth,
this._authService,
this._prefs, [http.Client? client],
) : _client = client ?? http.Client();
this._prefs, [
http.Client? client,
AuthService? authService,
]) : _authService = authService ?? FirebaseAuthService(),
_client = client ?? http.Client();
GitHubUploadState get state => _state;
String get statusMessage => _statusMessage;
@ -69,21 +74,18 @@ class GitHubSyncService extends ChangeNotifier {
}
bool isRideUploaded(int rideId) {
final scoped = _userScopedKey('motorcloud_uploaded_ride', rideId);
if (scoped == null) return false;
return _prefs.getBool(scoped) ?? false;
final key = _userScopedKey('motorcloud_uploaded_ride', rideId);
return key != null && (_prefs.getBool(key) ?? false);
}
String? getRideCommitSha(int rideId) {
final scoped = _userScopedKey('motorcloud_ride_remote_id', rideId);
if (scoped == null) return null;
return _prefs.getString(scoped);
final key = _userScopedKey('motorcloud_ride_remote_id', rideId);
return key == null ? null : _prefs.getString(key);
}
String? getRideUploadTimestamp(int rideId) {
final scoped = _userScopedKey('motorcloud_uploaded_at_ride', rideId);
if (scoped == null) return null;
return _prefs.getString(scoped);
final key = _userScopedKey('motorcloud_uploaded_at_ride', rideId);
return key == null ? null : _prefs.getString(key);
}
void queueRideForUpload(int rideId) {
@ -105,23 +107,23 @@ 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.
/// Deliberately ignores any configured GitHub PAT. A user's ride must be
/// written to that user's MotorCloud account, never to the developer's
/// public repository.
Future<GitHubUploadResult> uploadRide(int rideId, {bool isManual = false}) async {
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) {
final token = _githubAuth.getToken();
if (token == null || token.isEmpty) {
return GitHubUploadResult(
success: false,
message: 'GitHub export requires an explicitly configured GitHub token.',
);
}
return _uploadToGitHub(rideId, githubToken);
return _uploadToGitHub(rideId, token);
}
Future<GitHubUploadResult> _uploadToMotorCloud(int rideId) async {
@ -137,7 +139,12 @@ class GitHubSyncService extends ChangeNotifier {
_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');
return GitHubUploadResult(
success: true,
message: _statusMessage,
commitSha: getRideCommitSha(rideId),
targetPath: 'MotorCloud',
);
}
_state = GitHubUploadState.uploading;
@ -145,25 +152,25 @@ class GitHubSyncService extends ChangeNotifier {
notifyListeners();
try {
final provider = FirebaseSyncProvider(
db: _db,
authService: _authService,
);
final result = await provider.uploadRide(
SyncItem(
localRideId: rideId,
providerId: 'firebase',
createdAt: DateTime.now(),
),
);
final provider = FirebaseSyncProvider(db: _db, authService: _authService);
final result = await provider.uploadRide(SyncItem(
localRideId: rideId,
providerId: 'firebase',
createdAt: DateTime.now(),
));
if (result.success) {
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!);
await _prefs.setBool(_userScopedKey('motorcloud_uploaded_ride', rideId)!, true);
await _prefs.setString(
_userScopedKey('motorcloud_uploaded_at_ride', rideId)!,
DateTime.now().toIso8601String(),
);
if (result.remoteId != null) {
await _prefs.setString(
_userScopedKey('motorcloud_ride_remote_id', rideId)!,
result.remoteId!,
);
}
_uploadQueue.remove(rideId);
_state = GitHubUploadState.success;
_statusMessage = 'Ride #$rideId backed up to your MotorCloud account';
@ -323,7 +330,7 @@ class GitHubSyncService extends ChangeNotifier {
final githubSyncServiceProvider = ChangeNotifierProvider<GitHubSyncService>((ref) {
final db = ref.watch(databaseProvider);
final githubAuth = ref.watch(githubAuthServiceProvider);
final authService = ref.watch(authServiceProvider);
final prefs = ref.watch(sharedPreferencesProvider);
return GitHubSyncService(db, githubAuth, authService, prefs);
final authService = ref.watch(authServiceProvider);
return GitHubSyncService(db, githubAuth, prefs, null, authService);
});