refactor: Repository 패턴 적용 및 Clean Architecture 완성
## 주요 변경사항 ### 🏗️ Architecture - Repository 패턴 전면 도입 (인터페이스/구현체 분리) - Domain Layer에 Repository 인터페이스 정의 - Data Layer에 Repository 구현체 배치 - UseCase 의존성을 Service에서 Repository로 전환 ### 📦 Dependency Injection - GetIt 기반 DI Container 재구성 (lib/injection_container.dart) - Repository 인터페이스와 구현체 등록 - Service와 Repository 공존 (마이그레이션 기간) ### 🔄 Migration Status 완료: - License 모듈 (6개 UseCase) - Warehouse Location 모듈 (5개 UseCase) 진행중: - Auth 모듈 (2/5 UseCase) - Company 모듈 (1/6 UseCase) 대기: - User 모듈 (7개 UseCase) - Equipment 모듈 (4개 UseCase) ### 🎯 Controller 통합 - 중복 Controller 제거 (with_usecase 버전) - 단일 Controller로 통합 - UseCase 패턴 직접 적용 ### 🧹 코드 정리 - 임시 파일 제거 (test_*.md, task.md) - Node.js 아티팩트 제거 (package.json) - 불필요한 테스트 파일 정리 ### ✅ 테스트 개선 - Real API 중심 테스트 구조 - Mock 제거, 실제 API 엔드포인트 사용 - 통합 테스트 프레임워크 강화 ## 기술적 영향 - 의존성 역전 원칙 적용 - 레이어 간 결합도 감소 - 테스트 용이성 향상 - 확장성 및 유지보수성 개선 ## 다음 단계 1. User/Equipment 모듈 Repository 마이그레이션 2. Service Layer 점진적 제거 3. 캐싱 전략 구현 4. 성능 최적화
This commit is contained in:
@@ -2,14 +2,14 @@ import 'package:flutter/material.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:superport/core/errors/failures.dart';
|
||||
import 'package:superport/data/models/auth/login_request.dart';
|
||||
import 'package:superport/di/injection_container.dart';
|
||||
import 'package:superport/injection_container.dart';
|
||||
import 'package:superport/services/auth_service.dart';
|
||||
import 'package:superport/services/health_test_service.dart';
|
||||
import 'package:superport/services/health_check_service.dart';
|
||||
|
||||
/// 로그인 화면의 상태 및 비즈니스 로직을 담당하는 ChangeNotifier 기반 컨트롤러
|
||||
class LoginController extends ChangeNotifier {
|
||||
final AuthService _authService = inject<AuthService>();
|
||||
final AuthService _authService = sl<AuthService>();
|
||||
final HealthCheckService _healthCheckService = HealthCheckService();
|
||||
/// 아이디 입력 컨트롤러
|
||||
final TextEditingController idController = TextEditingController();
|
||||
|
||||
@@ -1,193 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import '../../../core/errors/failures.dart';
|
||||
import '../../../domain/usecases/base_usecase.dart';
|
||||
import '../../../domain/usecases/auth/login_usecase.dart';
|
||||
import '../../../domain/usecases/auth/check_auth_status_usecase.dart';
|
||||
import '../../../services/auth_service.dart';
|
||||
import '../../../services/health_check_service.dart';
|
||||
import '../../../di/injection_container.dart';
|
||||
|
||||
/// UseCase를 활용한 로그인 화면 컨트롤러
|
||||
/// 비즈니스 로직을 UseCase로 분리하여 테스트 용이성과 재사용성 향상
|
||||
class LoginControllerWithUseCase extends ChangeNotifier {
|
||||
// UseCases
|
||||
late final LoginUseCase _loginUseCase;
|
||||
late final CheckAuthStatusUseCase _checkAuthStatusUseCase;
|
||||
|
||||
// Services
|
||||
final HealthCheckService _healthCheckService = HealthCheckService();
|
||||
|
||||
// UI Controllers
|
||||
final TextEditingController idController = TextEditingController();
|
||||
final TextEditingController pwController = TextEditingController();
|
||||
|
||||
// Focus Nodes
|
||||
final FocusNode idFocus = FocusNode();
|
||||
final FocusNode pwFocus = FocusNode();
|
||||
|
||||
// State
|
||||
bool saveId = false;
|
||||
bool _isLoading = false;
|
||||
String? _errorMessage;
|
||||
|
||||
// Getters
|
||||
bool get isLoading => _isLoading;
|
||||
String? get errorMessage => _errorMessage;
|
||||
|
||||
LoginControllerWithUseCase() {
|
||||
// UseCase 초기화
|
||||
final authService = inject<AuthService>();
|
||||
_loginUseCase = LoginUseCase(authService);
|
||||
_checkAuthStatusUseCase = CheckAuthStatusUseCase(authService);
|
||||
|
||||
// 초기 인증 상태 확인
|
||||
_checkInitialAuthStatus();
|
||||
}
|
||||
|
||||
/// 초기 인증 상태 확인
|
||||
Future<void> _checkInitialAuthStatus() async {
|
||||
final result = await _checkAuthStatusUseCase(const NoParams());
|
||||
result.fold(
|
||||
(failure) => debugPrint('인증 상태 확인 실패: ${failure.message}'),
|
||||
(isAuthenticated) {
|
||||
if (isAuthenticated) {
|
||||
debugPrint('이미 로그인된 상태입니다.');
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 아이디 저장 체크박스 상태 변경
|
||||
void setSaveId(bool value) {
|
||||
saveId = value;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// 에러 메시지 초기화
|
||||
void clearError() {
|
||||
_errorMessage = null;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// 로그인 처리
|
||||
Future<bool> login() async {
|
||||
// 입력값 검증 (UI 레벨)
|
||||
if (idController.text.trim().isEmpty) {
|
||||
_errorMessage = '아이디 또는 이메일을 입력해주세요.';
|
||||
notifyListeners();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pwController.text.isEmpty) {
|
||||
_errorMessage = '비밀번호를 입력해주세요.';
|
||||
notifyListeners();
|
||||
return false;
|
||||
}
|
||||
|
||||
// 로딩 시작
|
||||
_isLoading = true;
|
||||
_errorMessage = null;
|
||||
notifyListeners();
|
||||
|
||||
// 입력값이 이메일인지 username인지 판단
|
||||
final inputValue = idController.text.trim();
|
||||
final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
|
||||
final isEmail = emailRegex.hasMatch(inputValue);
|
||||
|
||||
try {
|
||||
// UseCase 실행
|
||||
final params = LoginParams(
|
||||
email: isEmail ? inputValue : '$inputValue@superport.kr', // username인 경우 임시 도메인 추가
|
||||
password: pwController.text,
|
||||
);
|
||||
|
||||
debugPrint('[LoginController] 로그인 시도: ${params.email}');
|
||||
|
||||
final result = await _loginUseCase(params).timeout(
|
||||
const Duration(seconds: 10),
|
||||
onTimeout: () async {
|
||||
debugPrint('[LoginController] 로그인 요청 타임아웃');
|
||||
return Left(NetworkFailure(
|
||||
message: '요청 시간이 초과되었습니다. 네트워크 연결을 확인해주세요.',
|
||||
));
|
||||
},
|
||||
);
|
||||
|
||||
return result.fold(
|
||||
(failure) {
|
||||
debugPrint('[LoginController] 로그인 실패: ${failure.message}');
|
||||
|
||||
// 실패 타입에 따른 메시지 처리
|
||||
if (failure is ValidationFailure) {
|
||||
_errorMessage = failure.message;
|
||||
} else if (failure is AuthenticationFailure) {
|
||||
_errorMessage = '이메일 또는 비밀번호가 올바르지 않습니다.';
|
||||
} else if (failure is NetworkFailure) {
|
||||
_errorMessage = '네트워크 연결을 확인해주세요.';
|
||||
} else if (failure is ServerFailure) {
|
||||
_errorMessage = '서버 오류가 발생했습니다.\n잠시 후 다시 시도해주세요.';
|
||||
} else {
|
||||
_errorMessage = failure.message;
|
||||
}
|
||||
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
return false;
|
||||
},
|
||||
(loginResponse) {
|
||||
debugPrint('[LoginController] 로그인 성공');
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
return true;
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('[LoginController] 예상치 못한 에러: $e');
|
||||
_errorMessage = '로그인 중 오류가 발생했습니다.';
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 헬스체크 실행
|
||||
Future<bool> performHealthCheck() async {
|
||||
debugPrint('[LoginController] 헬스체크 시작');
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
final healthResult = await _healthCheckService.checkHealth();
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
|
||||
// HealthCheckService가 Map을 반환하는 경우 적절히 변환
|
||||
final isHealthy = healthResult is bool ? healthResult :
|
||||
(healthResult is Map && healthResult['status'] == 'healthy');
|
||||
|
||||
if (isHealthy == false) {
|
||||
_errorMessage = '서버와 연결할 수 없습니다.\n잠시 후 다시 시도해주세요.';
|
||||
notifyListeners();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
debugPrint('[LoginController] 헬스체크 실패: $e');
|
||||
_errorMessage = '서버 상태 확인 중 오류가 발생했습니다.';
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
idController.dispose();
|
||||
pwController.dispose();
|
||||
idFocus.dispose();
|
||||
pwFocus.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user