This repository has been archived on 2026-08-19. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
melo-app/lib/services/cloud_service.dart
T
Dustin 36c744d6e3 v2.31 — Login, Einstellungen & Cloud Sync Redesign
- Login-Screen im Melo-Design (Schwarz+Rot) mit Baka-Auth JWT
- AuthService fuer Token-Management ueber baka-net.de/auth
- Registrierungs-Modus + 'Ohne Login fortfahren'
- Navidrome-Credentials optional im Login aufklappbar

- SettingsScreen ersetzt AlertDialog
- BottomNav navigiert statt Dialog zu triggern
- Cloud Sync, Server verbinden, Diagnosedaten, App-Info, Abmelden

- CloudEinstellungen-Dialog entfernt, alles in CloudScreen konsolidiert
- Hardcode login('Baka','') entfernt → AuthService.benutzer
- Gradient-Statuskarte, animierte Segment-Intervall-Auswahl
- JWT Authorization statt Klartext-Passwort

- AppConfig: API-Key defaultValue entfernt (kein Fallback-Leak)
- Build crasht ohne --dart-define MELO_API_KEY

Build: split APK (arm64 19.6M, armeabi 17.1M, x86_64 21.1M)
2026-08-01 15:03:22 +02:00

172 lines
5.1 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<bool> 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<String, String> get _authHeader {
final token = AuthService().token;
final headers = <String, String>{
'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<Map?> status() => _get('/api/cloud/status');
Future<List<Map>> listSongs() async {
final r = await _get('/api/cloud/list');
return List<Map>.from(r?['songs'] ?? []);
}
Future<String?> 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<bool> 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<bool> 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<Map?> _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<String?> share(List<String> 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<Map?> 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<List<Map>> 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<Map>.from(d['changes'] ?? []);
}
} catch (_) {}
return [];
}
Future<List<Map>> 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<Map>.from(jsonDecode(r.body)['songs'] ?? []);
}
} catch (_) {}
return [];
}
Future<bool> 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;
}
}
}