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';