feat: migrate prosappco from Firebase to NestJS REST API (Fase 2)
- Replace all Firebase* repositories with Api* repositories using HTTP + SharedPreferences JWT - Remove Firebase.initializeApp() and firebase_messaging background handler from main.dart - Update DI (app_di.dart) to inject Api* repositories instead of Firebase* ones - Replace all Timestamp/cloud_firestore usage with ISO 8601 String dates - Stub PhoneVerificationService (Firebase phone OTP → backend OTP when implemented) - Add ApiService singleton with JWT management in lib/services/ - Legacy firebase_*_repository.dart files preserved for Fase 4 cleanup Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
726cf12fd2
commit
733384091c
@@ -2,3 +2,4 @@ library chat_repository;
|
||||
|
||||
export 'src/entities/entities.dart';
|
||||
export 'src/repositories/firebase_chat_repository.dart';
|
||||
export 'src/repositories/api_chat_repository.dart';
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:chat_repository/chat_repository.dart';
|
||||
|
||||
const _base = 'https://backend.prosapp.co/api/v1';
|
||||
|
||||
/// API-backed replacement for FirebaseChatRepository.
|
||||
/// Mirrors the same public API so existing blocs work without changes.
|
||||
class ApiChatRepository {
|
||||
String? _token;
|
||||
|
||||
Future<String?> _getToken() async {
|
||||
if (_token != null) return _token;
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
return _token = prefs.getString('token');
|
||||
}
|
||||
|
||||
Future<Map<String, String>> _headers() async {
|
||||
final t = await _getToken();
|
||||
return {
|
||||
'Content-Type': 'application/json',
|
||||
if (t != null) 'Authorization': 'Bearer $t',
|
||||
};
|
||||
}
|
||||
|
||||
ChatEntity _chatFromApi(Map<String, dynamic> json) {
|
||||
final rawMessages = json['messages'] as List? ?? [];
|
||||
final messages = rawMessages
|
||||
.map((m) => MessageEntity.fromDocument(m as Map<String, dynamic>))
|
||||
.toList();
|
||||
return ChatEntity(
|
||||
id: json['id']?.toString(),
|
||||
userId: json['user_id']?.toString() ?? json['userId']?.toString() ?? '',
|
||||
professionalId: json['professional_id']?.toString() ??
|
||||
json['professionalId']?.toString() ??
|
||||
'',
|
||||
messages: messages,
|
||||
);
|
||||
}
|
||||
|
||||
MessageEntity _msgFromApi(Map<String, dynamic> json) {
|
||||
return MessageEntity(
|
||||
ownerId: json['owner_id']?.toString() ?? json['sender_id']?.toString() ?? '',
|
||||
content: json['content']?.toString() ?? '',
|
||||
createdAt: json['created_at'] != null
|
||||
? DateTime.tryParse(json['created_at'].toString()) ?? DateTime.now()
|
||||
: DateTime.now(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Get or create a chat session. Maps to POST /chat/start/:professionalUserId.
|
||||
/// [chatId] here is used as the professional's userId for the REST call.
|
||||
Future<ChatEntity> createNewChat(
|
||||
String chatId, String userId, String professionalId) async {
|
||||
final res = await http.post(
|
||||
Uri.parse('$_base/chat/start/$professionalId'),
|
||||
headers: await _headers(),
|
||||
);
|
||||
final data = jsonDecode(res.body) as Map<String, dynamic>;
|
||||
return _chatFromApi(data);
|
||||
}
|
||||
|
||||
/// Streams a single chat by its ID. Fetches once and emits.
|
||||
Stream<ChatEntity?> getChatById(String chatId) {
|
||||
final controller = StreamController<ChatEntity?>();
|
||||
_fetchChat(chatId).then((chat) {
|
||||
controller.add(chat);
|
||||
controller.close();
|
||||
}).catchError((e) {
|
||||
controller.add(null);
|
||||
controller.close();
|
||||
});
|
||||
return controller.stream;
|
||||
}
|
||||
|
||||
Future<ChatEntity?> _fetchChat(String chatId) async {
|
||||
try {
|
||||
// Try to get messages for this chat — if the chat exists it'll succeed
|
||||
final res = await http.get(
|
||||
Uri.parse('$_base/chat/$chatId/messages'),
|
||||
headers: await _headers(),
|
||||
);
|
||||
if (res.statusCode == 404) return null;
|
||||
final messages = jsonDecode(res.body) as List? ?? [];
|
||||
return ChatEntity(
|
||||
id: chatId,
|
||||
userId: '',
|
||||
professionalId: '',
|
||||
messages: messages
|
||||
.map((m) => _msgFromApi(m as Map<String, dynamic>))
|
||||
.toList(),
|
||||
);
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> sendMessage(String chatId, MessageEntity message) async {
|
||||
await http.post(
|
||||
Uri.parse('$_base/chat/$chatId/message'),
|
||||
headers: await _headers(),
|
||||
body: jsonEncode({'content': message.content}),
|
||||
);
|
||||
}
|
||||
|
||||
/// Get all chats for the current user.
|
||||
Future<List<ChatEntity>> getMyChats() async {
|
||||
try {
|
||||
final res = await http.get(
|
||||
Uri.parse('$_base/chat/my'),
|
||||
headers: await _headers(),
|
||||
);
|
||||
final data = jsonDecode(res.body) as List? ?? [];
|
||||
return data.map((e) => _chatFromApi(e as Map<String, dynamic>)).toList();
|
||||
} catch (_) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,10 @@ dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
equatable: ^2.0.5
|
||||
http: ^1.1.0
|
||||
shared_preferences: ^2.0.10
|
||||
|
||||
# Firebase
|
||||
# Firebase kept for FirebaseChatRepository (legacy fallback)
|
||||
cloud_firestore: ^4.15.4
|
||||
firebase_core: ^2.25.4
|
||||
|
||||
@@ -22,4 +24,4 @@ dev_dependencies:
|
||||
sdk: flutter
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
uses-material-design: true
|
||||
|
||||
Reference in New Issue
Block a user