update review api.
This commit is contained in:
@@ -0,0 +1,341 @@
|
||||
/// Remote Data Source: Reviews
|
||||
///
|
||||
/// Handles API calls for review operations.
|
||||
library;
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:worker/core/errors/exceptions.dart';
|
||||
import 'package:worker/core/network/dio_client.dart';
|
||||
import 'package:worker/features/reviews/data/models/review_model.dart';
|
||||
import 'package:worker/features/reviews/data/models/review_response_model.dart';
|
||||
import 'package:worker/features/reviews/data/models/review_statistics_model.dart';
|
||||
|
||||
/// Reviews remote data source interface
|
||||
abstract class ReviewsRemoteDataSource {
|
||||
/// Get reviews for a product (legacy - returns only reviews)
|
||||
Future<List<ReviewModel>> getProductReviews({
|
||||
required String itemId,
|
||||
int limitPageLength = 10,
|
||||
int limitStart = 0,
|
||||
});
|
||||
|
||||
/// Get complete review response with statistics
|
||||
Future<ReviewResponseModel> getProductReviewsWithStats({
|
||||
required String itemId,
|
||||
int limitPageLength = 10,
|
||||
int limitStart = 0,
|
||||
});
|
||||
|
||||
/// Submit a review (create or update)
|
||||
Future<void> submitReview({
|
||||
required String itemId,
|
||||
required double rating,
|
||||
required String comment,
|
||||
String? name,
|
||||
});
|
||||
|
||||
/// Delete a review
|
||||
Future<void> deleteReview({required String name});
|
||||
}
|
||||
|
||||
/// Reviews remote data source implementation
|
||||
class ReviewsRemoteDataSourceImpl implements ReviewsRemoteDataSource {
|
||||
const ReviewsRemoteDataSourceImpl(this._dioClient);
|
||||
|
||||
final DioClient _dioClient;
|
||||
|
||||
// API endpoints
|
||||
static const String _getListEndpoint =
|
||||
'/api/method/building_material.building_material.api.item_feedback.get_list';
|
||||
static const String _updateEndpoint =
|
||||
'/api/method/building_material.building_material.api.item_feedback.update';
|
||||
static const String _deleteEndpoint =
|
||||
'/api/method/building_material.building_material.api.item_feedback.delete';
|
||||
|
||||
@override
|
||||
Future<List<ReviewModel>> getProductReviews({
|
||||
required String itemId,
|
||||
int limitPageLength = 10,
|
||||
int limitStart = 0,
|
||||
}) async {
|
||||
try {
|
||||
final response = await _dioClient.post(
|
||||
_getListEndpoint,
|
||||
data: {
|
||||
'limit_page_length': limitPageLength,
|
||||
'limit_start': limitStart,
|
||||
'item_id': itemId,
|
||||
},
|
||||
);
|
||||
|
||||
// Handle response
|
||||
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||
final data = response.data;
|
||||
|
||||
// Try different response formats
|
||||
List<dynamic> reviewsJson;
|
||||
|
||||
if (data is Map<String, dynamic>) {
|
||||
// Frappe typically returns: { "message": {...} }
|
||||
if (data.containsKey('message')) {
|
||||
final message = data['message'];
|
||||
if (message is List) {
|
||||
reviewsJson = message;
|
||||
} else if (message is Map<String, dynamic>) {
|
||||
// New API format: { "message": { "feedbacks": [...], "statistics": {...} } }
|
||||
if (message.containsKey('feedbacks')) {
|
||||
reviewsJson = message['feedbacks'] as List;
|
||||
} else if (message.containsKey('data')) {
|
||||
// Alternative: { "message": { "data": [...] } }
|
||||
reviewsJson = message['data'] as List;
|
||||
} else {
|
||||
throw const ParseException(
|
||||
'Unexpected response format: message map has no feedbacks or data key',
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw const ParseException(
|
||||
'Unexpected response format: message is not a list or map',
|
||||
);
|
||||
}
|
||||
} else if (data.containsKey('data')) {
|
||||
// Alternative: { "data": [...] }
|
||||
reviewsJson = data['data'] as List;
|
||||
} else if (data.containsKey('feedbacks')) {
|
||||
// Direct feedbacks key
|
||||
reviewsJson = data['feedbacks'] as List;
|
||||
} else {
|
||||
throw const ParseException(
|
||||
'Unexpected response format: no message, data, or feedbacks key',
|
||||
);
|
||||
}
|
||||
} else if (data is List) {
|
||||
// Direct list response
|
||||
reviewsJson = data;
|
||||
} else {
|
||||
throw ParseException(
|
||||
'Unexpected response format: ${data.runtimeType}',
|
||||
);
|
||||
}
|
||||
|
||||
// Parse reviews
|
||||
return reviewsJson
|
||||
.map((json) => ReviewModel.fromJson(json as Map<String, dynamic>))
|
||||
.toList();
|
||||
} else {
|
||||
throw NetworkException(
|
||||
'Failed to fetch reviews',
|
||||
statusCode: response.statusCode,
|
||||
data: response.data,
|
||||
);
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
throw _handleDioException(e);
|
||||
} on ParseException {
|
||||
rethrow;
|
||||
} catch (e, stackTrace) {
|
||||
throw UnknownException(
|
||||
'Failed to fetch reviews: ${e.toString()}',
|
||||
e,
|
||||
stackTrace,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ReviewResponseModel> getProductReviewsWithStats({
|
||||
required String itemId,
|
||||
int limitPageLength = 10,
|
||||
int limitStart = 0,
|
||||
}) async {
|
||||
try {
|
||||
final response = await _dioClient.post(
|
||||
_getListEndpoint,
|
||||
data: {
|
||||
'limit_page_length': limitPageLength,
|
||||
'limit_start': limitStart,
|
||||
'item_id': itemId,
|
||||
},
|
||||
);
|
||||
|
||||
// Handle response
|
||||
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||
final data = response.data;
|
||||
|
||||
// Parse the message object
|
||||
if (data is Map<String, dynamic> && data.containsKey('message')) {
|
||||
final message = data['message'];
|
||||
|
||||
if (message is Map<String, dynamic>) {
|
||||
// New API format with complete response
|
||||
return ReviewResponseModel.fromJson(message);
|
||||
} else {
|
||||
throw const ParseException(
|
||||
'Unexpected response format: message is not a map',
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw const ParseException(
|
||||
'Unexpected response format: no message key',
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw NetworkException(
|
||||
'Failed to fetch reviews',
|
||||
statusCode: response.statusCode,
|
||||
data: response.data,
|
||||
);
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
throw _handleDioException(e);
|
||||
} on ParseException {
|
||||
rethrow;
|
||||
} catch (e, stackTrace) {
|
||||
throw UnknownException(
|
||||
'Failed to fetch reviews: ${e.toString()}',
|
||||
e,
|
||||
stackTrace,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> submitReview({
|
||||
required String itemId,
|
||||
required double rating,
|
||||
required String comment,
|
||||
String? name,
|
||||
}) async {
|
||||
try {
|
||||
final data = {
|
||||
'item_id': itemId,
|
||||
'rating': rating,
|
||||
'comment': comment,
|
||||
if (name != null) 'name': name,
|
||||
};
|
||||
|
||||
final response = await _dioClient.post(
|
||||
_updateEndpoint,
|
||||
data: data,
|
||||
);
|
||||
|
||||
// Handle response
|
||||
if (response.statusCode != 200 && response.statusCode != 201) {
|
||||
throw NetworkException(
|
||||
'Failed to submit review',
|
||||
statusCode: response.statusCode,
|
||||
data: response.data,
|
||||
);
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
throw _handleDioException(e);
|
||||
} catch (e, stackTrace) {
|
||||
throw UnknownException(
|
||||
'Failed to submit review: ${e.toString()}',
|
||||
e,
|
||||
stackTrace,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteReview({required String name}) async {
|
||||
try {
|
||||
final response = await _dioClient.post(
|
||||
_deleteEndpoint,
|
||||
data: {'name': name},
|
||||
);
|
||||
|
||||
// Handle response
|
||||
if (response.statusCode != 200 && response.statusCode != 204) {
|
||||
throw NetworkException(
|
||||
'Failed to delete review',
|
||||
statusCode: response.statusCode,
|
||||
data: response.data,
|
||||
);
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
throw _handleDioException(e);
|
||||
} catch (e, stackTrace) {
|
||||
throw UnknownException(
|
||||
'Failed to delete review: ${e.toString()}',
|
||||
e,
|
||||
stackTrace,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle Dio exceptions and convert to app exceptions
|
||||
Exception _handleDioException(DioException e) {
|
||||
switch (e.type) {
|
||||
case DioExceptionType.connectionTimeout:
|
||||
case DioExceptionType.sendTimeout:
|
||||
case DioExceptionType.receiveTimeout:
|
||||
return const TimeoutException();
|
||||
|
||||
case DioExceptionType.connectionError:
|
||||
return const NoInternetException();
|
||||
|
||||
case DioExceptionType.badResponse:
|
||||
final statusCode = e.response?.statusCode;
|
||||
final data = e.response?.data;
|
||||
|
||||
// Extract error message from response if available
|
||||
String? errorMessage;
|
||||
if (data is Map<String, dynamic>) {
|
||||
errorMessage = data['message'] as String? ??
|
||||
data['error'] as String? ??
|
||||
data['exc'] as String?;
|
||||
}
|
||||
|
||||
switch (statusCode) {
|
||||
case 400:
|
||||
return BadRequestException(
|
||||
errorMessage ?? 'Dữ liệu không hợp lệ',
|
||||
);
|
||||
case 401:
|
||||
return UnauthorizedException(
|
||||
errorMessage ?? 'Phiên đăng nhập hết hạn. Vui lòng đăng nhập lại.',
|
||||
);
|
||||
case 403:
|
||||
return const ForbiddenException();
|
||||
case 404:
|
||||
return NotFoundException(
|
||||
errorMessage ?? 'Không tìm thấy đánh giá',
|
||||
);
|
||||
case 409:
|
||||
return ConflictException(
|
||||
errorMessage ?? 'Đánh giá đã tồn tại',
|
||||
);
|
||||
case 429:
|
||||
return const RateLimitException();
|
||||
case 500:
|
||||
case 502:
|
||||
case 503:
|
||||
case 504:
|
||||
return ServerException(
|
||||
errorMessage ?? 'Lỗi máy chủ. Vui lòng thử lại sau.',
|
||||
statusCode,
|
||||
);
|
||||
default:
|
||||
return NetworkException(
|
||||
errorMessage ?? 'Lỗi mạng không xác định',
|
||||
statusCode: statusCode,
|
||||
data: data,
|
||||
);
|
||||
}
|
||||
|
||||
case DioExceptionType.cancel:
|
||||
return const NetworkException('Yêu cầu đã bị hủy');
|
||||
|
||||
case DioExceptionType.badCertificate:
|
||||
return const NetworkException('Lỗi chứng chỉ SSL');
|
||||
|
||||
case DioExceptionType.unknown:
|
||||
default:
|
||||
return NetworkException(
|
||||
'Lỗi kết nối: ${e.message ?? "Unknown error"}',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user