import 'dart:convert'; import 'dart:io'; import 'package:http/http.dart' as http; import '../config/app_config.dart'; import '../services/auth_service.dart'; import '../services/melo_logger.dart'; /// Cloud-Sync Service für Melo Registry /// Authentifizierung via Baka-Auth JWT-Token class CloudService { static String get _base => AppConfig.cloudUrl; String _user = ''; /// Login mit Baka-Auth – Token wird aus AuthService bezogen Future login(String user) async { _user = user; try { final r = await http .get(Uri.parse('$_base/api/cloud/status'), headers: _authHeader) .timeout(const Duration(seconds: 5)); return r.statusCode == 200; } catch (_) { return false; } } Map get _authHeader { final token = AuthService().token; final headers = { 'X-API-Key': AppConfig.ytProxyApiKey, }; if (_user.isNotEmpty) { headers['X-User'] = _user; } // Baka-Auth JWT Token mitsenden falls vorhanden if (token != null && token.isNotEmpty) { headers['Authorization'] = 'Bearer $token'; } return headers; } Future status() => _get('/api/cloud/status'); Future> listSongs() async { final r = await _get('/api/cloud/list'); return List.from(r?['songs'] ?? []); } Future upload(String filepath, String filename) async { try { final req = http.MultipartRequest('POST', Uri.parse('$_base/api/cloud/upload')); req.headers.addAll(_authHeader); req.files.add(await http.MultipartFile.fromPath('file', filepath, filename: filename)); final resp = await req.send().timeout(const Duration(seconds: 120)); final body = jsonDecode(await resp.stream.bytesToString()); return body['song_id'] as String?; } catch (e) { MeloLogger().fehler('cloud_upload', e); return null; } } Future download(String songId, String destPath) async { try { final r = await http .get(Uri.parse('$_base/api/cloud/download/$songId'), headers: _authHeader) .timeout(const Duration(seconds: 120)); if (r.statusCode == 200) { await File(destPath).writeAsBytes(r.bodyBytes); return true; } return false; } catch (e) { MeloLogger().fehler('cloud_download', e); return false; } } Future delete(String songId) async { try { final r = await http .post(Uri.parse('$_base/api/cloud/delete'), headers: {..._authHeader, 'Content-Type': 'application/json'}, body: jsonEncode({'song_id': songId})) .timeout(const Duration(seconds: 10)); return r.statusCode == 200; } catch (_) { return false; } } Future _get(String path) async { try { final r = await http .get(Uri.parse('$_base$path'), headers: _authHeader) .timeout(const Duration(seconds: 10)); if (r.statusCode == 200) return jsonDecode(r.body); } catch (_) {} return null; } Future share(List songIds) async { try { final r = await http .post(Uri.parse('$_base/api/cloud/share'), headers: {..._authHeader, 'Content-Type': 'application/json'}, body: jsonEncode({'song_ids': songIds})) .timeout(const Duration(seconds: 10)); if (r.statusCode == 200) { final d = jsonDecode(r.body); return d['code'] as String?; } } catch (_) {} return null; } Future importCode(String code) async { try { final r = await http .post(Uri.parse('$_base/api/cloud/import'), headers: {..._authHeader, 'Content-Type': 'application/json'}, body: jsonEncode({'code': code})) .timeout(const Duration(seconds: 10)); if (r.statusCode == 200) return jsonDecode(r.body); } catch (_) {} return null; } Future> syncChanges(String since) async { try { final r = await http .post(Uri.parse('$_base/api/cloud/sync'), headers: {..._authHeader, 'Content-Type': 'application/json'}, body: jsonEncode({'since': since})) .timeout(const Duration(seconds: 10)); if (r.statusCode == 200) { final d = jsonDecode(r.body); return List.from(d['changes'] ?? []); } } catch (_) {} return []; } Future> globalList() async { try { final r = await http .get(Uri.parse('$_base/api/cloud/global'), headers: _authHeader) .timeout(const Duration(seconds: 10)); if (r.statusCode == 200) { return List.from(jsonDecode(r.body)['songs'] ?? []); } } catch (_) {} return []; } Future toggleGlobal(String songId) async { try { final r = await http .post(Uri.parse('$_base/api/cloud/toggle-global'), headers: {..._authHeader, 'Content-Type': 'application/json'}, body: jsonEncode({'song_id': songId})) .timeout(const Duration(seconds: 10)); return r.statusCode == 200; } catch (_) { return false; } } }