MotorSignal-App/lib/services/github/github_auth_service.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);
});