feat: migrate Firebase → NestJS REST API backend

Replace all Firebase SDK (Auth, Firestore, Storage) with HTTP calls
to backend.prosapp.co/api/v1:

- New ApiService singleton (JWT token, GET/POST/PATCH/DELETE/upload)
- auth_provider: Firebase Auth → /auth/login, /auth/register, /auth/phone/*
- services_provider + calendar_services_provider → /services endpoints
- professional_provider + professionals_provider → /professional-info, /users/professionals
- profile_form_provider + professional_form_provider → /users/me, /storage/upload
- cities/professions/settings providers → /cities, /professions, /settings
- firebase_chat_repository → polling via /chats endpoints (3s interval)
- firebase_score_repository → polling via /comments endpoints (10s interval)
- professional_detail_provider → /users/:id + /professional-info/:id
- dashboard_view: Firestore.add → POST /services
- chat_view + rating_view: FirebaseAuth.uid → AuthProvider.user.id
- Models: Timestamp → String for createdAt fields
- google_fonts upgraded to ^8.1.0 (Dart 3.12 compat)
- Remove firebase_core, firebase_auth, cloud_firestore, firebase_storage from pubspec

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-06-18 15:53:39 -05:00
co-authored by Claude Sonnet 4.6
parent 15175c1b91
commit 60216fd0e3
29 changed files with 440 additions and 1304 deletions
+21 -30
View File
@@ -1,43 +1,34 @@
import 'dart:developer';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'dart:async';
import 'package:prosapp_web_app/models/chat_entity.dart';
import 'package:prosapp_web_app/models/message_entity.dart';
import 'package:prosapp_web_app/services/api_service.dart';
// ponytail: renamed to ApiChatRepository but kept filename to avoid breaking imports
class FirebaseChatRepository {
final chatCollection = FirebaseFirestore.instance.collection('chats');
final _api = ApiService.instance;
Stream<ChatEntity?> getChatById(String chatId) {
return chatCollection.doc(chatId).snapshots().map((snapshot) {
Stream<ChatEntity?> getChatById(String chatId) async* {
while (true) {
try {
if (snapshot.exists) {
return ChatEntity.fromDocument(snapshot.data()!);
} else {
return null;
}
} catch (e) {
log(e.toString());
return null;
final data = await _api.get('/chats/$chatId');
yield ChatEntity.fromDocument(data as Map<String, dynamic>);
} catch (_) {
yield null;
}
});
await Future.delayed(const Duration(seconds: 3));
}
}
Future<ChatEntity> createNewChat(
String chatId, String userId, String professionalId) async {
ChatEntity chat = ChatEntity(
id: chatId,
userId: userId,
professionalId: professionalId,
messages: const [],
);
await chatCollection.doc(chatId).set(chat.toDocument());
return chat;
Future<ChatEntity> createNewChat(String chatId, String userId, String professionalId) async {
final data = await _api.post('/chats', {
'id': chatId,
'user_id': userId,
'professional_id': professionalId,
});
return ChatEntity.fromDocument(data as Map<String, dynamic>);
}
sendMessage(String chatId, MessageEntity message) {
chatCollection.doc(chatId).update({
'messages': FieldValue.arrayUnion([message.toDocument()])
});
Future<void> sendMessage(String chatId, MessageEntity message) async {
await _api.post('/chats/$chatId/messages', message.toDocument());
}
}
+33 -72
View File
@@ -1,91 +1,52 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:prosapp_web_app/models/comment_entity.dart';
import 'package:prosapp_web_app/models/score_entity.dart';
import 'package:prosapp_web_app/services/api_service.dart';
// ponytail: renamed to ApiScoreRepository but kept filename to avoid breaking imports
class FirebaseScoreRepository {
final reputationsCollection =
FirebaseFirestore.instance.collection('reputations');
final commentsCollection = FirebaseFirestore.instance.collection('comments');
final _api = ApiService.instance;
Future<ReputationEntity> getReputationByUserId(String userId) async {
try {
final snap = await reputationsCollection.doc(userId).get();
return ReputationEntity.fromDocument(snap.data()!);
final data = await _api.get('/comments/reputation/$userId');
return ReputationEntity.fromDocument(data as Map<String, dynamic>);
} catch (e) {
return const ReputationEntity(
total: 0,
average: 0,
totalPro: 0,
averagePro: 0,
);
return const ReputationEntity(total: 0, average: 0, totalPro: 0, averagePro: 0);
}
}
Stream<ReputationEntity> streamReputation() {
return FirebaseAuth.instance.userChanges().asyncMap((user) async {
if (user != null) {
return await getReputationByUserId(user.uid);
} else {
return const ReputationEntity(
total: 0,
average: 0,
totalPro: 0,
averagePro: 0,
);
Stream<ReputationEntity> streamReputation(String userId) async* {
while (true) {
yield await getReputationByUserId(userId);
await Future.delayed(const Duration(seconds: 10));
}
}
Stream<List<CommentEntity>> getScoresForUser(String userId) async* {
while (true) {
try {
final data = await _api.get('/comments?destination_id=$userId&is_from_user=false') as List;
yield data.map((e) => CommentEntity.fromDocument(e as Map<String, dynamic>)).toList();
} catch (_) {
yield [];
}
});
await Future.delayed(const Duration(seconds: 10));
}
}
Stream<List<CommentEntity>> getScoresForUser(String userId) {
return commentsCollection
.where('destination_id', isEqualTo: userId)
.where('is_from_user', isEqualTo: false)
.snapshots()
.map((querySnapshot) => querySnapshot.docs
.map((doc) => CommentEntity.fromDocument(doc.data()))
.toList());
}
Stream<List<CommentEntity>> getScoresForProfessional(String userId) {
return commentsCollection
.where('destination_id', isEqualTo: userId)
.where('is_from_user', isEqualTo: true)
.snapshots()
.map((querySnapshot) => querySnapshot.docs
.map((doc) => CommentEntity.fromDocument(doc.data()))
.toList());
Stream<List<CommentEntity>> getScoresForProfessional(String userId) async* {
while (true) {
try {
final data = await _api.get('/comments?destination_id=$userId&is_from_user=true') as List;
yield data.map((e) => CommentEntity.fromDocument(e as Map<String, dynamic>)).toList();
} catch (_) {
yield [];
}
await Future.delayed(const Duration(seconds: 10));
}
}
Future<void> addComment(CommentEntity comment) async {
await commentsCollection.add(comment.toDocument());
final query = await commentsCollection
.where('destination_id', isEqualTo: comment.destinationId)
.where('is_from_user', isEqualTo: comment.isFromUser)
.get();
var total = 0.0;
var count = 0;
for (var doc in query.docs) {
final comment = CommentEntity.fromDocument(doc.data());
total += comment.score;
count++;
}
if (count > 0) {
final average = total / count;
if (comment.isFromUser) {
await reputationsCollection.doc(comment.destinationId).set(
{'total_pro': count, 'average_pro': average},
SetOptions(merge: true));
} else {
await reputationsCollection.doc(comment.destinationId).set({
'total': count,
'average': average,
}, SetOptions(merge: true));
}
}
await _api.post('/comments', comment.toDocument());
}
}