This commit is contained in:
2025-10-10 22:49:05 +07:00
parent 02941e2234
commit 38c16bf0b9
49 changed files with 2702 additions and 740 deletions

View File

@@ -0,0 +1,166 @@
import 'package:dio/dio.dart';
import '../models/category_model.dart';
import '../../../../core/network/dio_client.dart';
import '../../../../core/network/api_response.dart';
import '../../../../core/constants/api_constants.dart';
import '../../../../core/errors/exceptions.dart';
/// Category remote data source using API
abstract class CategoryRemoteDataSource {
/// Get all categories (public endpoint - no auth required)
Future<List<CategoryModel>> getAllCategories();
/// Get single category by ID (public endpoint - no auth required)
Future<CategoryModel> getCategoryById(String id);
/// Get category with its products with pagination (public endpoint)
/// Returns Map with 'category' and 'products' with pagination info
Future<Map<String, dynamic>> getCategoryWithProducts(
String id,
int page,
int limit,
);
}
class CategoryRemoteDataSourceImpl implements CategoryRemoteDataSource {
final DioClient client;
CategoryRemoteDataSourceImpl(this.client);
@override
Future<List<CategoryModel>> getAllCategories() async {
try {
final response = await client.get(ApiConstants.categories);
// Parse API response using ApiResponse model
final apiResponse = ApiResponse<List<CategoryModel>>.fromJson(
response.data as Map<String, dynamic>,
(data) => (data as List<dynamic>)
.map((json) => CategoryModel.fromJson(json as Map<String, dynamic>))
.toList(),
);
if (!apiResponse.success) {
throw ServerException(
apiResponse.message ?? 'Failed to fetch categories',
);
}
return apiResponse.data;
} on DioException catch (e) {
throw _handleDioError(e);
} catch (e) {
throw ServerException('Failed to fetch categories: $e');
}
}
@override
Future<CategoryModel> getCategoryById(String id) async {
try {
final response = await client.get(ApiConstants.categoryById(id));
// Parse API response using ApiResponse model
final apiResponse = ApiResponse<CategoryModel>.fromJson(
response.data as Map<String, dynamic>,
(data) => CategoryModel.fromJson(data as Map<String, dynamic>),
);
if (!apiResponse.success) {
throw ServerException(
apiResponse.message ?? 'Failed to fetch category',
);
}
return apiResponse.data;
} on DioException catch (e) {
throw _handleDioError(e);
} catch (e) {
throw ServerException('Failed to fetch category: $e');
}
}
@override
Future<Map<String, dynamic>> getCategoryWithProducts(
String id,
int page,
int limit,
) async {
try {
final response = await client.get(
'${ApiConstants.categories}/$id/products',
queryParameters: {
'page': page,
'limit': limit,
},
);
// Parse API response - data contains category with nested products
final apiResponse = ApiResponse<Map<String, dynamic>>.fromJson(
response.data as Map<String, dynamic>,
(data) => data as Map<String, dynamic>,
);
if (!apiResponse.success) {
throw ServerException(
apiResponse.message ?? 'Failed to fetch category with products',
);
}
final responseData = apiResponse.data;
// Extract category info (excluding products array)
final categoryData = Map<String, dynamic>.from(responseData);
final products = categoryData.remove('products') as List<dynamic>? ?? [];
// Create category model from remaining data
final category = CategoryModel.fromJson(categoryData);
return {
'category': category,
'products': products,
'meta': apiResponse.meta?.toJson() ?? {},
};
} on DioException catch (e) {
throw _handleDioError(e);
} catch (e) {
throw ServerException('Failed to fetch category with products: $e');
}
}
/// Handle Dio errors and convert to custom exceptions
Exception _handleDioError(DioException error) {
switch (error.response?.statusCode) {
case ApiConstants.statusBadRequest:
return ValidationException(
error.response?.data['message'] ?? 'Invalid request',
);
case ApiConstants.statusUnauthorized:
return UnauthorizedException(
error.response?.data['message'] ?? 'Unauthorized access',
);
case ApiConstants.statusForbidden:
return UnauthorizedException(
error.response?.data['message'] ?? 'Access forbidden',
);
case ApiConstants.statusNotFound:
return NotFoundException(
error.response?.data['message'] ?? 'Category not found',
);
case ApiConstants.statusInternalServerError:
case ApiConstants.statusBadGateway:
case ApiConstants.statusServiceUnavailable:
return ServerException(
error.response?.data['message'] ?? 'Server error',
);
default:
if (error.type == DioExceptionType.connectionTimeout ||
error.type == DioExceptionType.receiveTimeout ||
error.type == DioExceptionType.sendTimeout) {
return NetworkException('Connection timeout');
} else if (error.type == DioExceptionType.connectionError) {
return NetworkException('No internet connection');
}
return ServerException('Unexpected error occurred');
}
}
}

View File

@@ -1,6 +1,7 @@
/// Export all categories data sources
///
/// Contains local data sources for categories
/// Contains local and remote data sources for categories
library;
export 'category_local_datasource.dart';
export 'category_remote_datasource.dart';

View File

@@ -27,6 +27,9 @@ class CategoryModel extends HiveObject {
@HiveField(6)
final DateTime createdAt;
@HiveField(7)
final DateTime updatedAt;
CategoryModel({
required this.id,
required this.name,
@@ -35,6 +38,7 @@ class CategoryModel extends HiveObject {
this.color,
required this.productCount,
required this.createdAt,
required this.updatedAt,
});
/// Convert to domain entity
@@ -47,6 +51,7 @@ class CategoryModel extends HiveObject {
color: color,
productCount: productCount,
createdAt: createdAt,
updatedAt: updatedAt,
);
}
@@ -60,6 +65,7 @@ class CategoryModel extends HiveObject {
color: category.color,
productCount: category.productCount,
createdAt: category.createdAt,
updatedAt: category.updatedAt,
);
}
@@ -71,8 +77,9 @@ class CategoryModel extends HiveObject {
description: json['description'] as String?,
iconPath: json['iconPath'] as String?,
color: json['color'] as String?,
productCount: json['productCount'] as int,
productCount: json['productCount'] as int? ?? 0,
createdAt: DateTime.parse(json['createdAt'] as String),
updatedAt: DateTime.parse(json['updatedAt'] as String),
);
}
@@ -86,6 +93,7 @@ class CategoryModel extends HiveObject {
'color': color,
'productCount': productCount,
'createdAt': createdAt.toIso8601String(),
'updatedAt': updatedAt.toIso8601String(),
};
}
@@ -98,6 +106,7 @@ class CategoryModel extends HiveObject {
String? color,
int? productCount,
DateTime? createdAt,
DateTime? updatedAt,
}) {
return CategoryModel(
id: id ?? this.id,
@@ -107,6 +116,7 @@ class CategoryModel extends HiveObject {
color: color ?? this.color,
productCount: productCount ?? this.productCount,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
);
}
}

View File

@@ -24,13 +24,14 @@ class CategoryModelAdapter extends TypeAdapter<CategoryModel> {
color: fields[4] as String?,
productCount: (fields[5] as num).toInt(),
createdAt: fields[6] as DateTime,
updatedAt: fields[7] as DateTime,
);
}
@override
void write(BinaryWriter writer, CategoryModel obj) {
writer
..writeByte(7)
..writeByte(8)
..writeByte(0)
..write(obj.id)
..writeByte(1)
@@ -44,7 +45,9 @@ class CategoryModelAdapter extends TypeAdapter<CategoryModel> {
..writeByte(5)
..write(obj.productCount)
..writeByte(6)
..write(obj.createdAt);
..write(obj.createdAt)
..writeByte(7)
..write(obj.updatedAt);
}
@override