import 'package:flutter/foundation.dart'; import 'package:injectable/injectable.dart'; import 'package:superport/core/constants/api_endpoints.dart'; import 'package:superport/core/errors/exceptions.dart'; import 'package:superport/data/datasources/remote/api_client.dart'; import 'package:superport/data/models/license/license_dto.dart'; import 'package:superport/data/models/license/license_request_dto.dart'; import 'package:superport/data/models/license/license_query_dto.dart'; abstract class LicenseRemoteDataSource { Future getLicenses({ int page = 1, int perPage = 20, bool? isActive, int? companyId, int? assignedUserId, String? licenseType, }); Future getLicenseById(int id); Future createLicense(CreateLicenseRequest request); Future updateLicense(int id, UpdateLicenseRequest request); Future deleteLicense(int id); Future assignLicense(int id, AssignLicenseRequest request); Future unassignLicense(int id); Future getExpiringLicenses({ int days = 30, int page = 1, int perPage = 20, }); } @LazySingleton(as: LicenseRemoteDataSource) class LicenseRemoteDataSourceImpl implements LicenseRemoteDataSource { final ApiClient _apiClient; LicenseRemoteDataSourceImpl({ required ApiClient apiClient, }) : _apiClient = apiClient; @override Future getLicenses({ int page = 1, int perPage = 20, bool? isActive, int? companyId, int? assignedUserId, String? licenseType, }) async { try { final queryParams = { 'page': page, 'per_page': perPage, }; if (isActive != null) queryParams['is_active'] = isActive; if (companyId != null) queryParams['company_id'] = companyId; if (assignedUserId != null) queryParams['assigned_user_id'] = assignedUserId; if (licenseType != null) queryParams['license_type'] = licenseType; final response = await _apiClient.get( ApiEndpoints.licenses, queryParameters: queryParams, ); if (response.data != null && response.data['success'] == true && response.data['data'] != null) { // API 응답이 배열인 경우와 객체인 경우를 모두 처리 final data = response.data['data']; if (data is List) { // 배열 응답을 LicenseListResponseDto 형식으로 변환 final List licenses = []; for (int i = 0; i < data.length; i++) { try { final item = data[i]; debugPrint('📑 Parsing license item $i: ${item['license_key']}'); // null 검사 및 기본값 설정 final licenseDto = LicenseDto.fromJson({ ...item, // 필수 필드 보장 'license_key': item['license_key'] ?? '', 'is_active': item['is_active'] ?? true, 'created_at': item['created_at'] ?? DateTime.now().toIso8601String(), 'updated_at': item['updated_at'] ?? DateTime.now().toIso8601String(), }); licenses.add(licenseDto); } catch (e, stackTrace) { debugPrint('❌ Error parsing license item $i: $e'); debugPrint('Item data: ${data[i]}'); debugPrint('Stack trace: $stackTrace'); // 파싱 실패한 항목은 건너뛰고 계속 continue; } } final pagination = response.data['pagination'] ?? {}; return LicenseListResponseDto( items: licenses, total: pagination['total'] ?? licenses.length, page: pagination['page'] ?? page, perPage: pagination['per_page'] ?? perPage, totalPages: pagination['total_pages'] ?? 1, ); } else if (data['items'] != null) { // 이미 LicenseListResponseDto 형식인 경우 return LicenseListResponseDto.fromJson(data); } else { // 예상치 못한 형식인 경우 throw ApiException( message: 'Unexpected response format for license list', ); } } else { throw ApiException( message: response.data?['error']?['message'] ?? 'Failed to fetch licenses', ); } } catch (e) { throw _handleError(e); } } @override Future getLicenseById(int id) async { try { final response = await _apiClient.get( '${ApiEndpoints.licenses}/$id', ); if (response.data != null && response.data['success'] == true && response.data['data'] != null) { return LicenseDto.fromJson(response.data['data']); } else { throw ApiException( message: response.data?['error']?['message'] ?? 'Failed to fetch license', ); } } catch (e) { throw _handleError(e); } } @override Future createLicense(CreateLicenseRequest request) async { try { final response = await _apiClient.post( ApiEndpoints.licenses, data: request.toJson(), ); if (response.data != null && response.data['success'] == true && response.data['data'] != null) { return LicenseDto.fromJson(response.data['data']); } else { throw ApiException( message: response.data?['error']?['message'] ?? 'Failed to fetch license', ); } } catch (e) { throw _handleError(e); } } @override Future updateLicense(int id, UpdateLicenseRequest request) async { try { final response = await _apiClient.put( '${ApiEndpoints.licenses}/$id', data: request.toJson(), ); if (response.data != null && response.data['success'] == true && response.data['data'] != null) { return LicenseDto.fromJson(response.data['data']); } else { throw ApiException( message: response.data?['error']?['message'] ?? 'Failed to fetch license', ); } } catch (e) { throw _handleError(e); } } @override Future deleteLicense(int id) async { try { await _apiClient.delete( '${ApiEndpoints.licenses}/$id', ); } catch (e) { throw _handleError(e); } } @override Future assignLicense(int id, AssignLicenseRequest request) async { try { final response = await _apiClient.patch( '${ApiEndpoints.licenses}/$id/assign', data: request.toJson(), ); if (response.data != null && response.data['success'] == true && response.data['data'] != null) { return LicenseDto.fromJson(response.data['data']); } else { throw ApiException( message: response.data?['error']?['message'] ?? 'Failed to fetch license', ); } } catch (e) { throw _handleError(e); } } @override Future unassignLicense(int id) async { try { final response = await _apiClient.patch( '${ApiEndpoints.licenses}/$id/unassign', ); if (response.data != null && response.data['success'] == true && response.data['data'] != null) { return LicenseDto.fromJson(response.data['data']); } else { throw ApiException( message: response.data?['error']?['message'] ?? 'Failed to fetch license', ); } } catch (e) { throw _handleError(e); } } @override Future getExpiringLicenses({ int days = 30, int page = 1, int perPage = 20, }) async { try { final queryParams = { 'days': days, 'page': page, 'per_page': perPage, }; final response = await _apiClient.get( '${ApiEndpoints.licenses}/expiring', queryParameters: queryParams, ); if (response.data != null && response.data['success'] == true && response.data['data'] != null) { // API 응답이 배열 형태인 경우 처리 final data = response.data['data']; final pagination = response.data['pagination'] ?? {}; if (data is List) { // 배열 응답을 ExpiringLicenseListDto 형식으로 변환 final List licenses = []; for (var item in data) { try { licenses.add(ExpiringLicenseDto.fromJson(item)); } catch (e) { debugPrint('❌ Error parsing expiring license: $e'); debugPrint('Item data: $item'); continue; } } return ExpiringLicenseListDto( items: licenses, total: pagination['total'] ?? licenses.length, page: pagination['page'] ?? page, perPage: pagination['per_page'] ?? perPage, totalPages: pagination['total_pages'] ?? 1, ); } else { // 이미 올바른 형식인 경우 return ExpiringLicenseListDto.fromJson(data); } } else { throw ApiException( message: response.data?['error']?['message'] ?? 'Failed to fetch expiring licenses', ); } } catch (e) { throw _handleError(e); } } Exception _handleError(dynamic error) { if (error is ApiException) { return error; } return ApiException( message: error.toString(), ); } }