105 lines
3.4 KiB
Dart
105 lines
3.4 KiB
Dart
import 'dart:convert';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
import '../../features/records/records_service.dart';
|
|
|
|
/// Secure credentials and repository configuration for GitHub ride sync
|
|
class GitHubAuthService {
|
|
static const String _keyToken = 'github_pat_token_enc';
|
|
static const String _keyRepo = 'github_target_repo';
|
|
static const String _keyBranch = 'github_target_branch';
|
|
|
|
static const String defaultRepo = 'bponders12-cell/ride_logs';
|
|
static const String defaultBranch = 'main';
|
|
|
|
final SharedPreferences _prefs;
|
|
final http.Client _client;
|
|
|
|
GitHubAuthService(this._prefs, [http.Client? client]) : _client = client ?? http.Client();
|
|
|
|
/// Reads stored Personal Access Token
|
|
String? getToken() {
|
|
final raw = _prefs.getString(_keyToken);
|
|
if (raw == null || raw.isEmpty) return null;
|
|
try {
|
|
return utf8.decode(base64.decode(raw));
|
|
} catch (_) {
|
|
return raw;
|
|
}
|
|
}
|
|
|
|
/// Stores Personal Access Token securely
|
|
Future<void> saveToken(String token) async {
|
|
final encoded = base64.encode(utf8.encode(token.trim()));
|
|
await _prefs.setString(_keyToken, encoded);
|
|
}
|
|
|
|
/// Clears stored token
|
|
Future<void> clearToken() async {
|
|
await _prefs.remove(_keyToken);
|
|
}
|
|
|
|
/// Target GitHub repository (default: bponders12-cell/ride_logs)
|
|
String getRepository() {
|
|
return _prefs.getString(_keyRepo) ?? defaultRepo;
|
|
}
|
|
|
|
Future<void> setRepository(String repo) async {
|
|
await _prefs.setString(_keyRepo, repo.trim());
|
|
}
|
|
|
|
/// Target branch (default: main)
|
|
String getBranch() {
|
|
return _prefs.getString(_keyBranch) ?? defaultBranch;
|
|
}
|
|
|
|
Future<void> setBranch(String branch) async {
|
|
await _prefs.setString(_keyBranch, branch.trim());
|
|
}
|
|
|
|
/// Tests connectivity and permissions against GitHub API
|
|
Future<Map<String, dynamic>> testConnection() async {
|
|
final token = getToken();
|
|
if (token == null || token.isEmpty) {
|
|
return {'success': false, 'message': 'No GitHub token configured'};
|
|
}
|
|
|
|
final repo = getRepository();
|
|
final url = Uri.parse('https://api.github.com/repos/$repo');
|
|
|
|
try {
|
|
final response = await _client.get(
|
|
url,
|
|
headers: {
|
|
'Authorization': 'Bearer $token',
|
|
'Accept': 'application/vnd.github.v3+json',
|
|
'User-Agent': 'MotorSignal-Telemetry/1.0.2',
|
|
},
|
|
).timeout(const Duration(seconds: 10));
|
|
|
|
if (response.statusCode == 200) {
|
|
final data = jsonDecode(response.body);
|
|
return {
|
|
'success': true,
|
|
'message': 'Connected to ${data['full_name']} (${data['default_branch']})',
|
|
'canPush': data['permissions']?['push'] ?? true,
|
|
};
|
|
} else if (response.statusCode == 401) {
|
|
return {'success': false, 'message': 'Authentication failed (401: Invalid Token)'};
|
|
} else if (response.statusCode == 404) {
|
|
return {'success': false, 'message': 'Repository not found or token lacks repo scope (404)'};
|
|
} else {
|
|
return {'success': false, 'message': 'GitHub API error (${response.statusCode})'};
|
|
}
|
|
} catch (e) {
|
|
return {'success': false, 'message': 'Connection error: $e'};
|
|
}
|
|
}
|
|
}
|
|
|
|
final githubAuthServiceProvider = Provider<GitHubAuthService>((ref) {
|
|
final prefs = ref.watch(sharedPreferencesProvider);
|
|
return GitHubAuthService(prefs);
|
|
});
|