// Backend API endpoints: // POST /auth/login body: {"username":"...","secret":"..."} // → {"access_token":"...","token_type":"bearer"} // GET /groups/my header: Authorization: Bearer TOKEN // → [{"id":"uuid","name":"...","type":"...","is_active":true}] // POST /groups/ body: {"name":"..."} // → GroupResponse // GET /groups/{id}/members → [{"user_id":"...","username":"...","role":"...","is_online":bool}] // POST /groups/{id}/invite body: {"username":"..."} // → {"message":"...","notification_id":"..."} // DELETE /groups/{id}/members/{user_id} → {"message":"..."} // GET /notifications/ → [NotificationResponse] // POST /notifications/{id}/respond body: {"is_accepted":bool} // // LiveKit token از طریق WebSocket دریافت می‌شود: // WS /ws/groups/{id}?token={jwt} import 'dart:convert'; import 'package:http/http.dart' as http; import '../config/app_config.dart'; import '../models/channel.dart'; import '../models/group_member.dart'; import '../models/app_notification.dart'; import 'auth_service.dart'; // ── Fake data (only used when AppConfig.debug == true) ──────────────────── const _fakeChannels = [ {'id': '1', 'name': 'تیم آلفا', 'type': 'PUBLIC', 'is_active': true}, {'id': '2', 'name': 'پشتیبانی میدانی', 'type': 'PRIVATE', 'is_active': true}, {'id': '3', 'name': 'فرماندهی', 'type': 'PUBLIC', 'is_active': true}, {'id': '4', 'name': 'گروه لجستیک', 'type': 'PRIVATE', 'is_active': true}, {'id': '5', 'name': 'واحد امنیت', 'type': 'PUBLIC', 'is_active': true}, ]; // ───────────────────────────────────────────────────────────────────────── class ApiService { final AuthService _auth; ApiService(this._auth); Future> _headers() async { final token = await _auth.getToken(); return { 'Content-Type': 'application/json', if (token != null) 'Authorization': 'Bearer $token', }; } // استخراج user_id از JWT token (بدون نیاز به کتابخانه) String? _extractUserIdFromJwt(String token) { try { final parts = token.split('.'); if (parts.length < 2) return null; var payload = parts[1]; while (payload.length % 4 != 0) { payload += '='; } final decoded = utf8.decode(base64Url.decode(payload)); final map = jsonDecode(decoded) as Map; return map['sub']?.toString(); } catch (_) { return null; } } // ── Auth ────────────────────────────────────────────────────────────── Future login(String username, String secret) async { if (AppConfig.debug) { await Future.delayed(const Duration(milliseconds: 600)); await _auth.saveAuth(token: 'debug_jwt', userId: 'debug_user'); return true; } try { final res = await http .post( Uri.parse('${AppConfig.baseUrl}/auth/login'), headers: {'Content-Type': 'application/json'}, body: jsonEncode({'username': username, 'secret': secret}), ) .timeout(const Duration(seconds: 10)); if (res.statusCode == 200) { final data = jsonDecode(res.body) as Map; final accessToken = data['access_token'] as String?; if (accessToken == null) return false; final userId = _extractUserIdFromJwt(accessToken); await _auth.saveAuth(token: accessToken, userId: userId); return true; } return false; } catch (_) { return false; } } // ── Groups ──────────────────────────────────────────────────────────── Future> getChannels() async { if (AppConfig.debug) { await Future.delayed(const Duration(milliseconds: 500)); return _fakeChannels .map((e) => Channel.fromJson(e as Map)) .toList(); } try { final res = await http .get( Uri.parse('${AppConfig.baseUrl}/groups/my'), headers: await _headers(), ) .timeout(const Duration(seconds: 10)); if (res.statusCode == 200) { final list = jsonDecode(res.body) as List; return list .map((e) => Channel.fromJson(e as Map)) .toList(); } return []; } catch (_) { return []; } } Future createGroup(String name) async { if (AppConfig.debug) { await Future.delayed(const Duration(milliseconds: 400)); return Channel(id: 'fake_${DateTime.now().millisecondsSinceEpoch}', name: name); } try { final res = await http .post( Uri.parse('${AppConfig.baseUrl}/groups/'), headers: await _headers(), body: jsonEncode({'name': name}), ) .timeout(const Duration(seconds: 10)); if (res.statusCode == 200 || res.statusCode == 201) { final data = jsonDecode(res.body) as Map; return Channel.fromJson(data); } return null; } catch (_) { return null; } } Future> getGroupMembers(String groupId) async { if (AppConfig.debug) { await Future.delayed(const Duration(milliseconds: 400)); return [ const GroupMember(userId: 'u1', username: 'علی', role: 'MANAGER', isOnline: true), const GroupMember(userId: 'u2', username: 'رضا', role: 'MEMBER', isOnline: false), const GroupMember(userId: 'u3', username: 'مریم', role: 'MEMBER', isOnline: true), ]; } try { final res = await http .get( Uri.parse('${AppConfig.baseUrl}/groups/$groupId/members'), headers: await _headers(), ) .timeout(const Duration(seconds: 10)); if (res.statusCode == 200) { final list = jsonDecode(res.body) as List; return list .map((e) => GroupMember.fromJson(e as Map)) .toList(); } return []; } catch (_) { return []; } } Future inviteMember(String groupId, String username) async { if (AppConfig.debug) { await Future.delayed(const Duration(milliseconds: 400)); return null; // success } try { final res = await http .post( Uri.parse('${AppConfig.baseUrl}/groups/$groupId/invite'), headers: await _headers(), body: jsonEncode({'username': username}), ) .timeout(const Duration(seconds: 10)); if (res.statusCode == 200 || res.statusCode == 201) { return null; // success, no error } // برگرداندن پیام خطا از بکند try { final data = jsonDecode(res.body) as Map; return (data['detail'] as String?) ?? 'خطا در ارسال دعوت'; } catch (_) { return 'خطا در ارسال دعوت'; } } catch (_) { return 'خطا در اتصال به سرور'; } } Future removeMember(String groupId, String userId) async { if (AppConfig.debug) { await Future.delayed(const Duration(milliseconds: 400)); return null; } try { final res = await http .delete( Uri.parse('${AppConfig.baseUrl}/groups/$groupId/members/$userId'), headers: await _headers(), ) .timeout(const Duration(seconds: 10)); if (res.statusCode == 200) return null; try { final data = jsonDecode(res.body) as Map; return (data['detail'] as String?) ?? 'خطا در حذف عضو'; } catch (_) { return 'خطا در حذف عضو'; } } catch (_) { return 'خطا در اتصال به سرور'; } } // ── Notifications ───────────────────────────────────────────────────── Future> getNotifications() async { if (AppConfig.debug) { await Future.delayed(const Duration(milliseconds: 400)); return [ AppNotification( id: 'n1', title: 'دعوت به گروه', description: 'شما به گروه "تیم آلفا" دعوت شده‌اید', type: 'JOIN_REQUEST', groupId: '1', isAccepted: null, receiverId: 'debug_user', senderId: 'u1', ), AppNotification( id: 'n2', title: 'اطلاعیه سیستم', description: 'سیستم به‌روزرسانی شد', type: 'PUBLIC', isAccepted: null, receiverId: 'debug_user', ), ]; } try { final res = await http .get( Uri.parse('${AppConfig.baseUrl}/notifications/'), headers: await _headers(), ) .timeout(const Duration(seconds: 10)); if (res.statusCode == 200) { final list = jsonDecode(res.body) as List; return list .map((e) => AppNotification.fromJson(e as Map)) .toList(); } return []; } catch (_) { return []; } } Future respondToNotification(String notificationId, bool isAccepted) async { if (AppConfig.debug) { await Future.delayed(const Duration(milliseconds: 400)); return null; } try { final res = await http .post( Uri.parse('${AppConfig.baseUrl}/notifications/$notificationId/respond'), headers: await _headers(), body: jsonEncode({'is_accepted': isAccepted}), ) .timeout(const Duration(seconds: 10)); if (res.statusCode == 200) return null; try { final data = jsonDecode(res.body) as Map; return (data['detail'] as String?) ?? 'خطا در پاسخ به اعلان'; } catch (_) { return 'خطا در پاسخ به اعلان'; } } catch (_) { return 'خطا در اتصال به سرور'; } } }