diff --git a/packages/professional_repository/lib/src/repositories/api_professional_repository.dart b/packages/professional_repository/lib/src/repositories/api_professional_repository.dart index 27c6db3..a9b6c10 100644 --- a/packages/professional_repository/lib/src/repositories/api_professional_repository.dart +++ b/packages/professional_repository/lib/src/repositories/api_professional_repository.dart @@ -198,8 +198,9 @@ class ApiProfessionalRepository { Future> getProfessionalInfo() async { try { - final data = await _get('/professionals') as List; - return data.map((e) => _fromApi(e as Map)).toList(); + final body = await _get('/professionals'); + final List raw = body is Map ? (body['data'] as List? ?? []) : (body as List? ?? []); + return raw.map((e) => _fromApi(e as Map)).toList(); } catch (_) { return []; } diff --git a/packages/score_repository/lib/src/repositories/api_score_repository.dart b/packages/score_repository/lib/src/repositories/api_score_repository.dart index 14f8033..aee5e0f 100644 --- a/packages/score_repository/lib/src/repositories/api_score_repository.dart +++ b/packages/score_repository/lib/src/repositories/api_score_repository.dart @@ -7,25 +7,18 @@ import 'package:score_repository/src/entities/comment_entity.dart'; const _base = 'https://backend.prosapp.co/api/v1'; -/// API-backed replacement for FirebaseScoreRepository. -/// Mirrors the same public API so existing blocs work without changes. class ApiScoreRepository { ReputationEntity? _reputation; - final StreamController _reputationController = - StreamController.broadcast(); + final _reputationController = StreamController.broadcast(); String? _token; ApiScoreRepository() { - _reputationController.add(_emptyReputation()); + _reputationController.add(_empty()); } - ReputationEntity _emptyReputation() => const ReputationEntity( - total: 0, - average: 0, - totalPro: 0, - averagePro: 0, - ); + ReputationEntity _empty() => + const ReputationEntity(total: 0, average: 0, totalPro: 0, averagePro: 0); Future _getToken() async { if (_token != null) return _token; @@ -43,7 +36,7 @@ class ApiScoreRepository { Stream streamReputation() => _reputationController.stream; - ReputationEntity getReputation() => _reputation ?? _emptyReputation(); + ReputationEntity getReputation() => _reputation ?? _empty(); Future getReputationByUserId(String userId) async { try { @@ -57,51 +50,33 @@ class ApiScoreRepository { _reputationController.add(rep); return rep; } catch (_) { - return _emptyReputation(); + return _empty(); } } - Stream> getScoresForUser(String userId) { + Stream> getScoresForUser(String userId) => + _streamComments('/comments/user/$userId'); + + Stream> getScoresForProfessional(String userId) => + _streamComments('/comments/professional/$userId'); + + Stream> _streamComments(String path) { final controller = StreamController>(); - _fetchComments(userId: userId, isFromUser: false).then((list) { + _fetchComments(path).then((list) { controller.add(list); controller.close(); - }).catchError((e) { + }).catchError((_) { controller.add([]); controller.close(); }); return controller.stream; } - Stream> getScoresForProfessional(String userId) { - final controller = StreamController>(); - _fetchComments(userId: userId, isFromUser: true).then((list) { - controller.add(list); - controller.close(); - }).catchError((e) { - controller.add([]); - controller.close(); - }); - return controller.stream; - } - - Future> _fetchComments({ - required String userId, - required bool isFromUser, - }) async { - try { - final res = await http.get( - Uri.parse('$_base/comments/reputation/$userId'), - headers: await _headers(), - ); - final data = jsonDecode(res.body); - if (data is! Map) return []; - // The endpoint returns reputation summary, not individual comments - // Return empty list since we only have aggregated data - return []; - } catch (_) { - return []; - } + Future> _fetchComments(String path) async { + final res = await http.get(Uri.parse('$_base$path'), headers: await _headers()); + final body = jsonDecode(res.body); + final List raw = body is Map ? (body['data'] as List? ?? []) : (body as List? ?? []); + return raw.map((e) => CommentEntity.fromDocument(e as Map)).toList(); } Future addComment(CommentEntity comment) async { @@ -110,7 +85,6 @@ class ApiScoreRepository { Uri.parse('$_base/comments'), headers: await _headers(), body: jsonEncode({ - 'author_id': comment.authorId, 'destination_id': comment.destinationId, 'service_id': comment.serviceId, 'content': comment.content, @@ -118,10 +92,7 @@ class ApiScoreRepository { 'is_from_user': comment.isFromUser, }), ); - // Refresh reputation after adding a comment await getReputationByUserId(comment.destinationId); - } catch (_) { - // Stub — do not crash if comment fails - } + } catch (_) {} } } diff --git a/packages/service_repository/lib/src/repositories/api_service_repository.dart b/packages/service_repository/lib/src/repositories/api_service_repository.dart index e3e3efa..93b10d8 100644 --- a/packages/service_repository/lib/src/repositories/api_service_repository.dart +++ b/packages/service_repository/lib/src/repositories/api_service_repository.dart @@ -7,8 +7,31 @@ import 'package:service_repository/service_repository.dart'; const _base = 'https://backend.prosapp.co/api/v1'; -/// API-backed replacement for FirebaseServiceRepository. -/// Mirrors the same public API so existing blocs work without changes. +// Backend uses string status; Flutter uses int-indexed enum +const _statusToString = { + 0: 'pending', + 1: 'accepted', + 2: 'denied', + 3: 'active', + 4: 'cancelled', + 5: 'completed', + 6: 'self_booked', +}; + +const _stringToStatusIndex = { + 'pending': 0, + 'accepted': 1, + 'acepted': 1, + 'denied': 2, + 'active': 3, + 'cancelled': 4, + 'canceled': 4, + 'completed': 5, + 'self_booked': 6, +}; + +const _locationToString = ['office', 'delivery']; + class ApiServiceRepository { String? _token; @@ -26,9 +49,8 @@ class ApiServiceRepository { }; } - Future _get(String path, {Map? query}) async { - final uri = Uri.parse('$_base$path').replace(queryParameters: query); - final res = await http.get(uri, headers: await _headers()); + Future _get(String path) async { + final res = await http.get(Uri.parse('$_base$path'), headers: await _headers()); return jsonDecode(res.body); } @@ -51,10 +73,11 @@ class ApiServiceRepository { } ServiceEntity _fromApi(Map json) { - String range1Hour1 = json['range1_hour1']?.toString() ?? '0:0'; - String range1Hour2 = json['range1_hour2']?.toString() ?? '0:0'; - - TimeOfDay parseTime(String s) { + TimeOfDay parseTime(String? s) { + if (s == null) return const TimeOfDay(hour: 0, minute: 0); + // Backend may return full ISO date or just "HH:MM" + final time = s.contains('T') ? DateTime.parse(s) : null; + if (time != null) return TimeOfDay(hour: time.hour, minute: time.minute); final parts = s.split(':'); return TimeOfDay( hour: int.tryParse(parts[0]) ?? 0, @@ -62,9 +85,22 @@ class ApiServiceRepository { ); } + final statusRaw = json['status']?.toString() ?? 'pending'; + final statusIndex = _stringToStatusIndex[statusRaw] ?? 0; + + final locationRaw = json['location_preference']?.toString() ?? 'office'; + final locationIndex = locationRaw == 'delivery' ? 1 : 0; + + // Use the professional's user_id if the response includes nested professional data. + // This keeps IDs consistent with the user-centric model used by the Flutter app. + final nestedPro = json['professionals']; + final professionalId = (nestedPro is Map) + ? (nestedPro['user_id']?.toString() ?? json['professional_id']?.toString() ?? '') + : json['professional_id']?.toString() ?? ''; + return ServiceEntity( id: json['id']?.toString(), - professionalId: json['professional_id']?.toString() ?? '', + professionalId: professionalId, professionalScored: json['professional_scored'] as bool? ?? false, userId: json['user_id']?.toString() ?? '', userScored: json['user_scored'] as bool? ?? false, @@ -75,31 +111,48 @@ class ApiServiceRepository { day: json['day']?.toString() ?? '', createdAt: json['created_at']?.toString() ?? DateTime.now().toIso8601String(), description: json['description']?.toString() ?? '', - range1Hour1: parseTime(range1Hour1), - range1Hour2: parseTime(range1Hour2), - rate: json['rate']?.toString() ?? '', - status: intToEnumService((json['status'] as num?)?.toInt() ?? 0), - location: intToEnum((json['location'] as num?)?.toInt() ?? 0), + range1Hour1: parseTime(json['range1_hour1']?.toString()), + range1Hour2: parseTime(json['range1_hour2']?.toString()), + rate: json['rate']?.toString() ?? '0', + status: intToEnumService(statusIndex), + location: intToEnum(locationIndex), ); } + List _parseList(dynamic body) { + final List raw = body is Map ? (body['data'] as List? ?? []) : (body as List? ?? []); + return raw.map((e) => _fromApi(e as Map)).toList(); + } + Future createService(ServiceEntity entity) async { - final data = await _post('/services', entity.toDocument()); + String fmt(TimeOfDay t) => + '${t.hour.toString().padLeft(2, '0')}:${t.minute.toString().padLeft(2, '0')}'; + + final data = await _post('/services', { + 'professional_id': entity.professionalId, + 'day': entity.day, + 'description': entity.description, + 'rate': double.tryParse(entity.rate) ?? 0, + 'range1_hour1': fmt(entity.range1Hour1), + 'range1_hour2': fmt(entity.range1Hour2), + 'address': entity.address, + 'latitude': entity.latitude, + 'longitude': entity.longitude, + 'location_preference': _locationToString[entity.location.index], + }); return data['id']?.toString() ?? ''; } Future updateServiceStatus(String serviceId, ServiceStatus newStatus) async { - await _patch('/services/$serviceId', {'status': enumToIntService(newStatus)}); + await _patch('/services/$serviceId/status', { + 'status': _statusToString[newStatus.index] ?? 'pending', + }); } Stream getService(String serviceId) { final controller = StreamController(); - _get('/services', query: {'id': serviceId}).then((data) { - if (data is List && data.isNotEmpty) { - controller.add(_fromApi(data.first as Map)); - } else if (data is Map) { - controller.add(_fromApi(data as Map)); - } + _get('/services/$serviceId').then((data) { + if (data is Map) controller.add(_fromApi(data as Map)); controller.close(); }).catchError((e) { controller.addError(e); @@ -109,75 +162,51 @@ class ApiServiceRepository { } Stream> getServicesForUser(String userId) { - return _streamList('/services', query: {'userId': userId}, statusFilter: [0, 1, 3]); + return _stream('/services/me'); } Stream> getServicesForProfessional(String professionalId) { - return _streamList('/services', query: {'professionalId': professionalId}, statusFilter: [1, 3]); + return _stream('/services/professional'); } Future> getServicesForProfessionalforCalendar(String professionalId) async { try { - final data = await _get('/services', query: {'professionalId': professionalId}); - if (data is! List) return []; - return data - .map((e) => _fromApi(e as Map)) - .where((s) => [0, 1, 2, 3, 6].contains(s.status.index)) - .toList(); + return _parseList(await _get('/services/professional/calendar')); } catch (_) { return []; } } Stream> getServicesHistoryForUser(String userId) { - return _streamList('/services', query: {'userId': userId}, statusFilter: [2, 4, 5]); + return _stream('/services/me/history'); } Stream> getServicesHistoryForProfessional(String professionalId) { - return _streamList('/services', query: {'professionalId': professionalId}, statusFilter: [2, 4, 5]); + return _stream('/services/professional/history'); } Stream> getPendingServicesForProfessional(String professionalId) { - return _streamList('/services', query: {'professionalId': professionalId}, statusFilter: [0]); + return _stream('/services/professional/requests'); } Future countPendingServicesForProfessional(String professionalId) async { try { - final data = await _get('/services', query: {'professionalId': professionalId}); - if (data is! List) return 0; - return data - .map((e) => _fromApi(e as Map)) - .where((s) => s.status == ServiceStatus.pending) - .length; + final body = await _get('/services/professional/requests'); + if (body is Map) return (body['meta']?['total'] as num?)?.toInt() ?? 0; + return (body as List).length; } catch (_) { return 0; } } - Future setProfessionalScored(String serviceId) async { - await _patch('/services/$serviceId', {'professional_scored': true}); - } + // Backend sets these automatically when POST /comments is called + Future setProfessionalScored(String serviceId) async {} + Future setUserScored(String serviceId) async {} - Future setUserScored(String serviceId) async { - await _patch('/services/$serviceId', {'user_scored': true}); - } - - Stream> _streamList( - String path, { - Map? query, - List statusFilter = const [], - }) { + Stream> _stream(String path) { final controller = StreamController>(); - _get(path, query: query).then((data) { - if (data is! List) { - controller.add([]); - } else { - var list = data.map((e) => _fromApi(e as Map)).toList(); - if (statusFilter.isNotEmpty) { - list = list.where((s) => statusFilter.contains(s.status.index)).toList(); - } - controller.add(list); - } + _get(path).then((data) { + controller.add(_parseList(data)); controller.close(); }).catchError((e) { controller.add([]); diff --git a/packages/user_repository/lib/src/repositories/api_user_repository.dart b/packages/user_repository/lib/src/repositories/api_user_repository.dart index 9e65fa3..a9a877f 100644 --- a/packages/user_repository/lib/src/repositories/api_user_repository.dart +++ b/packages/user_repository/lib/src/repositories/api_user_repository.dart @@ -201,7 +201,8 @@ class ApiUserRepository implements UserRepository { @override Future> getUsersProfessionalActive() async { try { - final data = await _get('/professionals') as List; + final body = await _get('/professionals'); + final List data = body is Map ? (body['data'] as List? ?? []) : (body as List? ?? []); final List result = []; for (final p in data) { final userId = p['user_id']?.toString();