Neda/Front/lib/services/api_service.dart
2026-03-08 09:53:45 +03:30

313 lines
10 KiB
Dart

// 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<Map<String, String>> _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<String, dynamic>;
return map['sub']?.toString();
} catch (_) {
return null;
}
}
// ── Auth ──────────────────────────────────────────────────────────────
Future<bool> 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<String, dynamic>;
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<List<Channel>> getChannels() async {
if (AppConfig.debug) {
await Future.delayed(const Duration(milliseconds: 500));
return _fakeChannels
.map((e) => Channel.fromJson(e as Map<String, dynamic>))
.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<String, dynamic>))
.toList();
}
return [];
} catch (_) {
return [];
}
}
Future<Channel?> 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<String, dynamic>;
return Channel.fromJson(data);
}
return null;
} catch (_) {
return null;
}
}
Future<List<GroupMember>> 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<String, dynamic>))
.toList();
}
return [];
} catch (_) {
return [];
}
}
Future<String?> 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<String, dynamic>;
return (data['detail'] as String?) ?? 'خطا در ارسال دعوت';
} catch (_) {
return 'خطا در ارسال دعوت';
}
} catch (_) {
return 'خطا در اتصال به سرور';
}
}
Future<String?> 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<String, dynamic>;
return (data['detail'] as String?) ?? 'خطا در حذف عضو';
} catch (_) {
return 'خطا در حذف عضو';
}
} catch (_) {
return 'خطا در اتصال به سرور';
}
}
// ── Notifications ─────────────────────────────────────────────────────
Future<List<AppNotification>> 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<String, dynamic>))
.toList();
}
return [];
} catch (_) {
return [];
}
}
Future<String?> 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<String, dynamic>;
return (data['detail'] as String?) ?? 'خطا در پاسخ به اعلان';
} catch (_) {
return 'خطا در پاسخ به اعلان';
}
} catch (_) {
return 'خطا در اتصال به سرور';
}
}
}