import 'package:dio/dio.dart'; import 'package:injectable/injectable.dart'; import 'package:superport/core/constants/api_endpoints.dart'; import 'package:superport/data/datasources/remote/api_client.dart'; import 'package:superport/data/models/zipcode_dto.dart'; abstract class ZipcodeRepository { /// 우편번호 검색 (페이지네이션 지원) Future search({ int page = 1, int limit = 20, String? search, String? sido, String? gu, }); /// 우편번호로 정확한 주소 조회 Future getByZipcode(int zipcode); /// 시도별 구 목록 조회 Future> getGuBySido(String sido); /// 전체 시도 목록 조회 Future> getAllSido(); } @Injectable(as: ZipcodeRepository) class ZipcodeRepositoryImpl implements ZipcodeRepository { final ApiClient _apiClient; ZipcodeRepositoryImpl(this._apiClient); @override Future search({ int page = 1, int limit = 20, String? search, String? sido, String? gu, }) async { try { final queryParams = { 'page': page, 'limit': limit, }; if (search != null && search.isNotEmpty) { queryParams['search'] = search; } if (sido != null && sido.isNotEmpty) { queryParams['sido'] = sido; } if (gu != null && gu.isNotEmpty) { queryParams['gu'] = gu; } final response = await _apiClient.dio.get( ApiEndpoints.zipcodes, queryParameters: queryParams, ); // API 응답 구조에 따라 파싱 if (response.data is Map) { // 페이지네이션 응답 형식 return ZipcodeListResponse.fromJson(response.data); } else if (response.data is List) { // 배열 직접 반환 형식 final zipcodes = (response.data as List) .map((json) => ZipcodeDto.fromJson(json)) .toList(); return ZipcodeListResponse( items: zipcodes, totalCount: zipcodes.length, currentPage: page, totalPages: 1, ); } else { throw Exception('예상치 못한 응답 형식입니다.'); } } on DioException catch (e) { throw _handleError(e); } } @override Future getByZipcode(int zipcode) async { try { final response = await _apiClient.dio.get( ApiEndpoints.zipcodes, queryParameters: { 'zipcode': zipcode, 'limit': 1, }, ); if (response.data is Map) { final listResponse = ZipcodeListResponse.fromJson(response.data); return listResponse.items.isNotEmpty ? listResponse.items.first : null; } return null; } on DioException catch (e) { throw _handleError(e); } } @override Future> getGuBySido(String sido) async { try { final response = await _apiClient.dio.get( ApiEndpoints.zipcodes, queryParameters: { 'sido': sido, 'limit': 1000, // 충분히 큰 값으로 모든 구 가져오기 }, ); if (response.data is Map) { final listResponse = ZipcodeListResponse.fromJson(response.data); // 중복 제거하고 구 목록만 추출 final guSet = {}; for (final zipcode in listResponse.items) { guSet.add(zipcode.gu); } final guList = guSet.toList()..sort(); return guList; } return []; } on DioException catch (e) { throw _handleError(e); } } @override Future> getAllSido() async { try { final response = await _apiClient.dio.get( ApiEndpoints.zipcodes, queryParameters: { 'limit': 1000, // 충분히 큰 값으로 모든 시도 가져오기 }, ); if (response.data is Map) { final listResponse = ZipcodeListResponse.fromJson(response.data); // 중복 제거하고 시도 목록만 추출 final sidoSet = {}; for (final zipcode in listResponse.items) { sidoSet.add(zipcode.sido); } final sidoList = sidoSet.toList()..sort(); return sidoList; } return []; } on DioException catch (e) { throw _handleError(e); } } Exception _handleError(DioException e) { switch (e.type) { case DioExceptionType.connectionTimeout: case DioExceptionType.sendTimeout: case DioExceptionType.receiveTimeout: return Exception('연결 시간이 초과되었습니다.'); case DioExceptionType.badResponse: final statusCode = e.response?.statusCode; final message = e.response?.data?['message'] ?? '서버 오류가 발생했습니다.'; return Exception('[$statusCode] $message'); case DioExceptionType.connectionError: return Exception('네트워크 연결을 확인해주세요.'); default: return Exception('알 수 없는 오류가 발생했습니다.'); } } }