fix: bind ride backup to shared Firebase identity and prevent GitHub routing
This commit is contained in:
parent
272a2b56ee
commit
f31e0b8eaa
|
|
@ -36,10 +36,10 @@ class GitHubUploadResult {
|
||||||
|
|
||||||
/// Compatibility facade for the historical ride-detail sync service.
|
/// Compatibility facade for the historical ride-detail sync service.
|
||||||
///
|
///
|
||||||
/// MotorSignal cloud backup is Firebase Authentication + Firestore + R2.
|
/// Normal MotorSignal cloud backup is Firebase Authentication + Firestore + R2.
|
||||||
/// The old GitHub PAT exporter remains available only through the explicit
|
/// The historical GitHub PAT exporter remains available only through the
|
||||||
/// `uploadRideToGitHub` developer/export path and is never used by the normal
|
/// explicit `uploadRideToGitHub` developer/export path and is never used by
|
||||||
/// History -> Upload Now action.
|
/// the normal History -> Upload Now action.
|
||||||
class GitHubSyncService extends ChangeNotifier {
|
class GitHubSyncService extends ChangeNotifier {
|
||||||
final AppDatabase _db;
|
final AppDatabase _db;
|
||||||
final GitHubAuthService _githubAuth;
|
final GitHubAuthService _githubAuth;
|
||||||
|
|
@ -51,12 +51,17 @@ class GitHubSyncService extends ChangeNotifier {
|
||||||
String _statusMessage = '';
|
String _statusMessage = '';
|
||||||
final Set<int> _uploadQueue = {};
|
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(
|
GitHubSyncService(
|
||||||
this._db,
|
this._db,
|
||||||
this._githubAuth,
|
this._githubAuth,
|
||||||
this._authService,
|
this._prefs, [
|
||||||
this._prefs, [http.Client? client],
|
http.Client? client,
|
||||||
) : _client = client ?? http.Client();
|
AuthService? authService,
|
||||||
|
]) : _authService = authService ?? FirebaseAuthService(),
|
||||||
|
_client = client ?? http.Client();
|
||||||
|
|
||||||
GitHubUploadState get state => _state;
|
GitHubUploadState get state => _state;
|
||||||
String get statusMessage => _statusMessage;
|
String get statusMessage => _statusMessage;
|
||||||
|
|
@ -69,21 +74,18 @@ class GitHubSyncService extends ChangeNotifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isRideUploaded(int rideId) {
|
bool isRideUploaded(int rideId) {
|
||||||
final scoped = _userScopedKey('motorcloud_uploaded_ride', rideId);
|
final key = _userScopedKey('motorcloud_uploaded_ride', rideId);
|
||||||
if (scoped == null) return false;
|
return key != null && (_prefs.getBool(key) ?? false);
|
||||||
return _prefs.getBool(scoped) ?? false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String? getRideCommitSha(int rideId) {
|
String? getRideCommitSha(int rideId) {
|
||||||
final scoped = _userScopedKey('motorcloud_ride_remote_id', rideId);
|
final key = _userScopedKey('motorcloud_ride_remote_id', rideId);
|
||||||
if (scoped == null) return null;
|
return key == null ? null : _prefs.getString(key);
|
||||||
return _prefs.getString(scoped);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String? getRideUploadTimestamp(int rideId) {
|
String? getRideUploadTimestamp(int rideId) {
|
||||||
final scoped = _userScopedKey('motorcloud_uploaded_at_ride', rideId);
|
final key = _userScopedKey('motorcloud_uploaded_at_ride', rideId);
|
||||||
if (scoped == null) return null;
|
return key == null ? null : _prefs.getString(key);
|
||||||
return _prefs.getString(scoped);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void queueRideForUpload(int rideId) {
|
void queueRideForUpload(int rideId) {
|
||||||
|
|
@ -105,23 +107,23 @@ class GitHubSyncService extends ChangeNotifier {
|
||||||
|
|
||||||
/// Normal History -> Upload Now operation.
|
/// Normal History -> Upload Now operation.
|
||||||
///
|
///
|
||||||
/// This deliberately ignores any configured GitHub PAT. A user's ride must
|
/// Deliberately ignores any configured GitHub PAT. A user's ride must be
|
||||||
/// be written to that user's MotorCloud account, not to a public developer
|
/// written to that user's MotorCloud account, never to the developer's
|
||||||
/// repository. Authentication and ownership are enforced by Firebase/R2.
|
/// public repository.
|
||||||
Future<GitHubUploadResult> uploadRide(int rideId, {bool isManual = false}) async {
|
Future<GitHubUploadResult> uploadRide(int rideId, {bool isManual = false}) async {
|
||||||
return _uploadToMotorCloud(rideId);
|
return _uploadToMotorCloud(rideId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Explicit legacy/developer export. Not used by the normal ride UI.
|
/// Explicit legacy/developer export. Not used by the normal ride UI.
|
||||||
Future<GitHubUploadResult> uploadRideToGitHub(int rideId) async {
|
Future<GitHubUploadResult> uploadRideToGitHub(int rideId) async {
|
||||||
final githubToken = _githubAuth.getToken();
|
final token = _githubAuth.getToken();
|
||||||
if (githubToken == null || githubToken.isEmpty) {
|
if (token == null || token.isEmpty) {
|
||||||
return GitHubUploadResult(
|
return GitHubUploadResult(
|
||||||
success: false,
|
success: false,
|
||||||
message: 'GitHub export requires an explicitly configured GitHub token.',
|
message: 'GitHub export requires an explicitly configured GitHub token.',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return _uploadToGitHub(rideId, githubToken);
|
return _uploadToGitHub(rideId, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<GitHubUploadResult> _uploadToMotorCloud(int rideId) async {
|
Future<GitHubUploadResult> _uploadToMotorCloud(int rideId) async {
|
||||||
|
|
@ -137,7 +139,12 @@ class GitHubSyncService extends ChangeNotifier {
|
||||||
_state = GitHubUploadState.success;
|
_state = GitHubUploadState.success;
|
||||||
_statusMessage = 'Ride #$rideId is already backed up to your MotorCloud account.';
|
_statusMessage = 'Ride #$rideId is already backed up to your MotorCloud account.';
|
||||||
notifyListeners();
|
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;
|
_state = GitHubUploadState.uploading;
|
||||||
|
|
@ -145,25 +152,25 @@ class GitHubSyncService extends ChangeNotifier {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final provider = FirebaseSyncProvider(
|
final provider = FirebaseSyncProvider(db: _db, authService: _authService);
|
||||||
db: _db,
|
final result = await provider.uploadRide(SyncItem(
|
||||||
authService: _authService,
|
localRideId: rideId,
|
||||||
);
|
providerId: 'firebase',
|
||||||
final result = await provider.uploadRide(
|
createdAt: DateTime.now(),
|
||||||
SyncItem(
|
));
|
||||||
localRideId: rideId,
|
|
||||||
providerId: 'firebase',
|
|
||||||
createdAt: DateTime.now(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
final uploadedKey = _userScopedKey('motorcloud_uploaded_ride', rideId)!;
|
await _prefs.setBool(_userScopedKey('motorcloud_uploaded_ride', rideId)!, true);
|
||||||
final timestampKey = _userScopedKey('motorcloud_uploaded_at_ride', rideId)!;
|
await _prefs.setString(
|
||||||
final remoteIdKey = _userScopedKey('motorcloud_ride_remote_id', rideId)!;
|
_userScopedKey('motorcloud_uploaded_at_ride', rideId)!,
|
||||||
await _prefs.setBool(uploadedKey, true);
|
DateTime.now().toIso8601String(),
|
||||||
await _prefs.setString(timestampKey, DateTime.now().toIso8601String());
|
);
|
||||||
if (result.remoteId != null) await _prefs.setString(remoteIdKey, result.remoteId!);
|
if (result.remoteId != null) {
|
||||||
|
await _prefs.setString(
|
||||||
|
_userScopedKey('motorcloud_ride_remote_id', rideId)!,
|
||||||
|
result.remoteId!,
|
||||||
|
);
|
||||||
|
}
|
||||||
_uploadQueue.remove(rideId);
|
_uploadQueue.remove(rideId);
|
||||||
_state = GitHubUploadState.success;
|
_state = GitHubUploadState.success;
|
||||||
_statusMessage = 'Ride #$rideId backed up to your MotorCloud account';
|
_statusMessage = 'Ride #$rideId backed up to your MotorCloud account';
|
||||||
|
|
@ -323,7 +330,7 @@ class GitHubSyncService extends ChangeNotifier {
|
||||||
final githubSyncServiceProvider = ChangeNotifierProvider<GitHubSyncService>((ref) {
|
final githubSyncServiceProvider = ChangeNotifierProvider<GitHubSyncService>((ref) {
|
||||||
final db = ref.watch(databaseProvider);
|
final db = ref.watch(databaseProvider);
|
||||||
final githubAuth = ref.watch(githubAuthServiceProvider);
|
final githubAuth = ref.watch(githubAuthServiceProvider);
|
||||||
final authService = ref.watch(authServiceProvider);
|
|
||||||
final prefs = ref.watch(sharedPreferencesProvider);
|
final prefs = ref.watch(sharedPreferencesProvider);
|
||||||
return GitHubSyncService(db, githubAuth, authService, prefs);
|
final authService = ref.watch(authServiceProvider);
|
||||||
|
return GitHubSyncService(db, githubAuth, prefs, null, authService);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue