update review api.
This commit is contained in:
116
lib/features/reviews/domain/entities/review.dart
Normal file
116
lib/features/reviews/domain/entities/review.dart
Normal file
@@ -0,0 +1,116 @@
|
||||
/// Domain Entity: Review
|
||||
///
|
||||
/// Represents a product review with rating and comment.
|
||||
library;
|
||||
|
||||
/// Review entity
|
||||
///
|
||||
/// Contains user feedback for a product including:
|
||||
/// - Unique review ID (name field from API)
|
||||
/// - Product item code
|
||||
/// - Rating (0-1 from API, converted to 0-5 stars for display)
|
||||
/// - Review comment text
|
||||
/// - Reviewer information
|
||||
/// - Review date
|
||||
class Review {
|
||||
const Review({
|
||||
required this.id,
|
||||
required this.itemId,
|
||||
required this.rating,
|
||||
required this.comment,
|
||||
this.reviewerName,
|
||||
this.reviewerEmail,
|
||||
this.reviewDate,
|
||||
});
|
||||
|
||||
/// Unique review identifier (format: ITEM-{item_id}-{user_email})
|
||||
final String id;
|
||||
|
||||
/// Product item code being reviewed
|
||||
final String itemId;
|
||||
|
||||
/// Rating from API (0-5 scale)
|
||||
/// Note: API already provides rating on 0-5 scale, no conversion needed
|
||||
final double rating;
|
||||
|
||||
/// Review comment text
|
||||
final String comment;
|
||||
|
||||
/// Name of the reviewer (if available)
|
||||
final String? reviewerName;
|
||||
|
||||
/// Email of the reviewer (if available)
|
||||
final String? reviewerEmail;
|
||||
|
||||
/// Date when the review was created (if available)
|
||||
final DateTime? reviewDate;
|
||||
|
||||
/// Get star rating rounded to nearest integer (0-5)
|
||||
///
|
||||
/// Examples:
|
||||
/// - API rating 0.5 = 1 star
|
||||
/// - API rating 2.25 = 2 stars
|
||||
/// - API rating 4.0 = 4 stars
|
||||
int get starsRating => rating.round();
|
||||
|
||||
/// Get rating as exact decimal (0-5 scale)
|
||||
///
|
||||
/// This is useful for average rating calculations and display
|
||||
/// API already returns this on 0-5 scale
|
||||
double get starsRatingDecimal => rating;
|
||||
|
||||
/// Copy with method for creating modified copies
|
||||
Review copyWith({
|
||||
String? id,
|
||||
String? itemId,
|
||||
double? rating,
|
||||
String? comment,
|
||||
String? reviewerName,
|
||||
String? reviewerEmail,
|
||||
DateTime? reviewDate,
|
||||
}) {
|
||||
return Review(
|
||||
id: id ?? this.id,
|
||||
itemId: itemId ?? this.itemId,
|
||||
rating: rating ?? this.rating,
|
||||
comment: comment ?? this.comment,
|
||||
reviewerName: reviewerName ?? this.reviewerName,
|
||||
reviewerEmail: reviewerEmail ?? this.reviewerEmail,
|
||||
reviewDate: reviewDate ?? this.reviewDate,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other is Review &&
|
||||
other.id == id &&
|
||||
other.itemId == itemId &&
|
||||
other.rating == rating &&
|
||||
other.comment == comment &&
|
||||
other.reviewerName == reviewerName &&
|
||||
other.reviewerEmail == reviewerEmail &&
|
||||
other.reviewDate == reviewDate;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return Object.hash(
|
||||
id,
|
||||
itemId,
|
||||
rating,
|
||||
comment,
|
||||
reviewerName,
|
||||
reviewerEmail,
|
||||
reviewDate,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Review(id: $id, itemId: $itemId, rating: $rating, '
|
||||
'starsRating: $starsRating, comment: ${comment.substring(0, comment.length > 30 ? 30 : comment.length)}..., '
|
||||
'reviewerName: $reviewerName, reviewDate: $reviewDate)';
|
||||
}
|
||||
}
|
||||
49
lib/features/reviews/domain/entities/review_statistics.dart
Normal file
49
lib/features/reviews/domain/entities/review_statistics.dart
Normal file
@@ -0,0 +1,49 @@
|
||||
/// Domain Entity: Review Statistics
|
||||
///
|
||||
/// Aggregate statistics for product reviews.
|
||||
library;
|
||||
|
||||
/// Review statistics entity
|
||||
///
|
||||
/// Contains aggregate data about reviews:
|
||||
/// - Total number of feedbacks
|
||||
/// - Average rating (0-5 scale)
|
||||
class ReviewStatistics {
|
||||
const ReviewStatistics({
|
||||
required this.totalFeedback,
|
||||
required this.averageRating,
|
||||
});
|
||||
|
||||
/// Total number of reviews/feedbacks
|
||||
final int totalFeedback;
|
||||
|
||||
/// Average rating (0-5 scale)
|
||||
/// Note: This is already on 0-5 scale from API, no conversion needed
|
||||
final double averageRating;
|
||||
|
||||
/// Check if there are any reviews
|
||||
bool get hasReviews => totalFeedback > 0;
|
||||
|
||||
/// Get star rating rounded to nearest 0.5
|
||||
/// Used for displaying star icons
|
||||
double get displayRating {
|
||||
return (averageRating * 2).round() / 2;
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other is ReviewStatistics &&
|
||||
other.totalFeedback == totalFeedback &&
|
||||
other.averageRating == averageRating;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(totalFeedback, averageRating);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ReviewStatistics(totalFeedback: $totalFeedback, averageRating: $averageRating)';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/// Domain Repository Interface: Reviews
|
||||
///
|
||||
/// Defines the contract for review data operations.
|
||||
library;
|
||||
|
||||
import 'package:worker/features/reviews/domain/entities/review.dart';
|
||||
import 'package:worker/features/reviews/domain/entities/review_statistics.dart';
|
||||
|
||||
/// Reviews repository interface
|
||||
///
|
||||
/// Defines methods for managing product reviews:
|
||||
/// - Fetching reviews for a product
|
||||
/// - Submitting new reviews
|
||||
/// - Updating existing reviews
|
||||
/// - Deleting reviews
|
||||
abstract class ReviewsRepository {
|
||||
/// Get reviews for a specific product
|
||||
///
|
||||
/// [itemId] - Product item code
|
||||
/// [limitPageLength] - Number of reviews per page (default: 10)
|
||||
/// [limitStart] - Pagination offset (default: 0)
|
||||
///
|
||||
/// Returns a list of [Review] entities
|
||||
///
|
||||
/// Throws:
|
||||
/// - [NetworkException] on network errors
|
||||
/// - [ServerException] on server errors
|
||||
/// - [ParseException] on JSON parsing errors
|
||||
Future<List<Review>> getProductReviews({
|
||||
required String itemId,
|
||||
int limitPageLength = 10,
|
||||
int limitStart = 0,
|
||||
});
|
||||
|
||||
/// Get review statistics for a product
|
||||
///
|
||||
/// [itemId] - Product item code
|
||||
///
|
||||
/// Returns [ReviewStatistics] with total count and average rating
|
||||
///
|
||||
/// Throws:
|
||||
/// - [NetworkException] on network errors
|
||||
/// - [ServerException] on server errors
|
||||
Future<ReviewStatistics> getProductReviewStatistics({
|
||||
required String itemId,
|
||||
});
|
||||
|
||||
/// Submit a new review or update an existing one
|
||||
///
|
||||
/// [itemId] - Product item code
|
||||
/// [rating] - Rating value (0-1 scale for API)
|
||||
/// [comment] - Review comment text
|
||||
/// [name] - Optional review ID for updates (format: ITEM-{item_id}-{user_email})
|
||||
///
|
||||
/// If [name] is provided, the review will be updated.
|
||||
/// If [name] is null, a new review will be created.
|
||||
///
|
||||
/// Throws:
|
||||
/// - [NetworkException] on network errors
|
||||
/// - [ServerException] on server errors
|
||||
/// - [ValidationException] on invalid data
|
||||
/// - [AuthException] if not authenticated
|
||||
Future<void> submitReview({
|
||||
required String itemId,
|
||||
required double rating,
|
||||
required String comment,
|
||||
String? name,
|
||||
});
|
||||
|
||||
/// Delete a review
|
||||
///
|
||||
/// [name] - Review ID to delete (format: ITEM-{item_id}-{user_email})
|
||||
///
|
||||
/// Throws:
|
||||
/// - [NetworkException] on network errors
|
||||
/// - [ServerException] on server errors
|
||||
/// - [NotFoundException] if review doesn't exist
|
||||
/// - [AuthException] if not authenticated or not authorized
|
||||
Future<void> deleteReview({
|
||||
required String name,
|
||||
});
|
||||
}
|
||||
24
lib/features/reviews/domain/usecases/delete_review.dart
Normal file
24
lib/features/reviews/domain/usecases/delete_review.dart
Normal file
@@ -0,0 +1,24 @@
|
||||
/// Use Case: Delete Review
|
||||
///
|
||||
/// Deletes a product review.
|
||||
library;
|
||||
|
||||
import 'package:worker/features/reviews/domain/repositories/reviews_repository.dart';
|
||||
|
||||
/// Use case for deleting a product review
|
||||
class DeleteReview {
|
||||
const DeleteReview(this._repository);
|
||||
|
||||
final ReviewsRepository _repository;
|
||||
|
||||
/// Execute the use case
|
||||
///
|
||||
/// [name] - Review ID to delete (format: ITEM-{item_id}-{user_email})
|
||||
Future<void> call({required String name}) async {
|
||||
if (name.trim().isEmpty) {
|
||||
throw ArgumentError('Review ID cannot be empty');
|
||||
}
|
||||
|
||||
await _repository.deleteReview(name: name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/// Use Case: Get Product Reviews
|
||||
///
|
||||
/// Fetches reviews for a specific product with pagination.
|
||||
library;
|
||||
|
||||
import 'package:worker/features/reviews/domain/entities/review.dart';
|
||||
import 'package:worker/features/reviews/domain/repositories/reviews_repository.dart';
|
||||
|
||||
/// Use case for getting product reviews
|
||||
class GetProductReviews {
|
||||
const GetProductReviews(this._repository);
|
||||
|
||||
final ReviewsRepository _repository;
|
||||
|
||||
/// Execute the use case
|
||||
///
|
||||
/// [itemId] - Product item code
|
||||
/// [limitPageLength] - Number of reviews per page (default: 10)
|
||||
/// [limitStart] - Pagination offset (default: 0)
|
||||
///
|
||||
/// Returns a list of [Review] entities sorted by date (newest first)
|
||||
Future<List<Review>> call({
|
||||
required String itemId,
|
||||
int limitPageLength = 10,
|
||||
int limitStart = 0,
|
||||
}) async {
|
||||
return await _repository.getProductReviews(
|
||||
itemId: itemId,
|
||||
limitPageLength: limitPageLength,
|
||||
limitStart: limitStart,
|
||||
);
|
||||
}
|
||||
}
|
||||
61
lib/features/reviews/domain/usecases/submit_review.dart
Normal file
61
lib/features/reviews/domain/usecases/submit_review.dart
Normal file
@@ -0,0 +1,61 @@
|
||||
/// Use Case: Submit Review
|
||||
///
|
||||
/// Submits a new product review or updates an existing one.
|
||||
library;
|
||||
|
||||
import 'package:worker/features/reviews/domain/repositories/reviews_repository.dart';
|
||||
|
||||
/// Use case for submitting a product review
|
||||
class SubmitReview {
|
||||
const SubmitReview(this._repository);
|
||||
|
||||
final ReviewsRepository _repository;
|
||||
|
||||
/// Execute the use case
|
||||
///
|
||||
/// [itemId] - Product item code
|
||||
/// [rating] - Rating value (0-1 scale for API)
|
||||
/// [comment] - Review comment text
|
||||
/// [name] - Optional review ID for updates
|
||||
///
|
||||
/// Note: The rating should be in 0-1 scale for the API.
|
||||
/// If you have a 1-5 star rating, convert it first: `stars / 5.0`
|
||||
Future<void> call({
|
||||
required String itemId,
|
||||
required double rating,
|
||||
required String comment,
|
||||
String? name,
|
||||
}) async {
|
||||
// Validate rating range (0-1)
|
||||
if (rating < 0 || rating > 1) {
|
||||
throw ArgumentError(
|
||||
'Rating must be between 0 and 1. Got: $rating. '
|
||||
'If you have a 1-5 star rating, convert it first: stars / 5.0',
|
||||
);
|
||||
}
|
||||
|
||||
// Validate comment length
|
||||
if (comment.trim().isEmpty) {
|
||||
throw ArgumentError('Review comment cannot be empty');
|
||||
}
|
||||
|
||||
if (comment.trim().length < 20) {
|
||||
throw ArgumentError(
|
||||
'Review comment must be at least 20 characters. Got: ${comment.trim().length}',
|
||||
);
|
||||
}
|
||||
|
||||
if (comment.length > 1000) {
|
||||
throw ArgumentError(
|
||||
'Review comment must not exceed 1000 characters. Got: ${comment.length}',
|
||||
);
|
||||
}
|
||||
|
||||
await _repository.submitReview(
|
||||
itemId: itemId,
|
||||
rating: rating,
|
||||
comment: comment.trim(),
|
||||
name: name,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user