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/auth_service.dart
T
Dustin 15993b1d0a v2.35 — Vollständiges Sync-System: Playlisten, Favoriten, Auto-Sync, Persistent Login, Benutzerdefinierte Namen
## Server (melo_cloud.py v3.0.0)
- Neue DB-Tabellen: user_playlists, user_playlist_songs, user_favorites, user_history, sync_meta
- Playlist-Endpunkte: GET/POST /api/cloud/playlists, GET /api/cloud/playlists/<id>, POST songs, DELETE songs, PUT positions
- Favoriten-Endpunkte: GET/POST /api/cloud/favorites, POST /api/cloud/favorites/toggle
- History-Endpunkte: GET/POST /api/cloud/history (mit Dedup)
- Rename-Endpunkt: POST /api/cloud/rename (custom_title/custom_artist)
- Sync-Endpunkte: POST /api/cloud/sync-all, GET /api/cloud/sync-status
- delete-all erweitert um Playlists + Favorites + History

## App
- db_helper.dart: Migration v4→v5 (cloud_id, cloud_title, cloud_artist, server_favorites, sync_metadata)
- auth_service.dart: Token-Validierung beim Start (online check + Offline-Fallback), Persistent Login
- cloud_service.dart: Neue Methoden für Playlists (CRUD), Favorites (get/sync/toggle), Rename, History, syncAll/syncStatus
- cloud_screen.dart: Komplett-Rewrite mit Sync-Modus (Manuell/Auto), Intervall-Auswahl, Sync-Fortschritt-Anzeige, Playlist-Verwaltung, Favoriten-Anzeige + Umbenennen, Sync-Info-Karte, nächster Sync
- main.dart: Auto-Sync beim App-Start (nur wenn konfiguriert + fällig)

## Flutter Analyze
- 0 neue Issues — nur 7 pre-existing
2026-08-01 19:32:35 +02:00

188 lines
5.8 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 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
import '../config/app_config.dart';
import '../services/melo_logger.dart';
/// Baka-Auth Service JWT-basierte Authentifizierung (v3 — Persistent Login)
/// Integriert mit https://baka-net.de/auth
class AuthService {
static final AuthService _instance = AuthService._();
factory AuthService() => _instance;
AuthService._();
String? _token;
String _user = '';
bool _initialisiert = false;
bool get istEingeloggt => _token != null && _token!.isNotEmpty;
String get benutzer => _user;
String? get token => _token;
/// Auth-Header für API-Requests
Map<String, String> get authHeader {
final headers = <String, String>{
'Content-Type': 'application/json',
};
if (_token != null && _token!.isNotEmpty) {
headers['Authorization'] = 'Bearer $_token';
}
return headers;
}
/// Lädt gespeicherten Token beim App-Start und validiert ihn
Future<bool> initialisieren() async {
if (_initialisiert) return istEingeloggt;
try {
final prefs = await SharedPreferences.getInstance();
_token = prefs.getString('baka_token');
_user = prefs.getString('baka_user') ?? '';
if (_token != null && _token!.isNotEmpty) {
// Token beim Server validieren (nicht blind vertrauen)
final valid = await _tokenOnlinePruefen();
if (!valid) {
// Token ist abgelaufen — nicht als eingeloggt behandeln
MeloLogger().zustand('auth_token_expired', {'user': _user});
_token = null;
_user = '';
await prefs.remove('baka_token');
await prefs.remove('baka_user');
} else {
MeloLogger().zustand('auth_restored', {'user': _user});
}
}
} catch (e) {
MeloLogger().fehler('auth_init', e);
}
_initialisiert = true;
return istEingeloggt;
}
/// Prüft Token beim Baka-Auth-Server (online)
Future<bool> _tokenOnlinePruefen() async {
if (_token == null) return false;
try {
final response = await http
.get(
Uri.parse('${AppConfig.authUrl}/verify'),
headers: authHeader,
)
.timeout(const Duration(seconds: 5));
return response.statusCode == 200;
} catch (_) {
// Bei Netzwerkfehler: Token lokal akzeptieren (Offline-Modus)
return true;
}
}
/// Login über Baka-Auth-Server
Future<AuthResult> login(String user, String password) async {
try {
final response = await http
.post(
Uri.parse('${AppConfig.authUrl}/login'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'username': user,
'password': password,
}),
)
.timeout(const Duration(seconds: 10));
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
_token = data['token'] as String?;
_user = user;
if (_token != null && _token!.isNotEmpty) {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('baka_token', _token!);
await prefs.setString('baka_user', _user);
MeloLogger().aktion('auth_login_ok', {'user': _user});
return AuthResult.ok;
}
final serverMsg = data['message'] as String? ??
data['error'] as String? ??
'Unbekannt';
return AuthResult.fehlgeschlagen(serverMsg);
}
String fehler = 'Login fehlgeschlagen (${response.statusCode})';
MeloLogger().fehler('auth_login_fail', fehler);
return AuthResult.fehlgeschlagen(fehler);
} catch (e) {
final msg = 'Keine Verbindung zum Auth-Server';
MeloLogger().fehler('auth_login_error', e);
return AuthResult.fehlgeschlagen(msg);
}
}
/// Registrierung über Baka-Auth-Server
Future<AuthResult> registrieren(
String user, String password, String email) async {
try {
final response = await http
.post(
Uri.parse('${AppConfig.authUrl}/register'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'username': user,
'password': password,
'email': email,
}),
)
.timeout(const Duration(seconds: 10));
if (response.statusCode == 200 || response.statusCode == 201) {
final data = jsonDecode(response.body);
_token = data['token'] as String?;
_user = user;
if (_token != null && _token!.isNotEmpty) {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('baka_token', _token!);
await prefs.setString('baka_user', _user);
MeloLogger().aktion('auth_register_ok', {'user': _user});
return AuthResult.ok;
}
}
String fehler = 'Registrierung fehlgeschlagen';
try {
final data = jsonDecode(response.body);
fehler = data['error'] as String? ?? fehler;
} catch (_) {}
return AuthResult.fehlgeschlagen(fehler);
} catch (e) {
return AuthResult.fehlgeschlagen('Keine Verbindung zum Server');
}
}
/// Token beim Server validieren (public)
Future<bool> tokenPruefen() async {
return _tokenOnlinePruefen();
}
/// Ausloggen Token löschen
Future<void> logout() async {
_token = null;
_user = '';
final prefs = await SharedPreferences.getInstance();
await prefs.remove('baka_token');
await prefs.remove('baka_user');
MeloLogger().aktion('auth_logout', {});
}
}
/// Ergebnis eines Auth-Versuchs
class AuthResult {
final bool erfolg;
final String? fehler;
const AuthResult._(this.erfolg, this.fehler);
static const ok = AuthResult._(true, null);
static AuthResult fehlgeschlagen(String msg) => AuthResult._(false, msg);
}