import 'package:dartz/dartz.dart'; import 'package:injectable/injectable.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../../core/errors/failures.dart'; import '../../domain/repositories/auth_repository.dart'; import '../datasources/remote/auth_remote_datasource.dart'; import '../models/auth/auth_user.dart'; import '../models/auth/login_request.dart'; import '../models/auth/login_response.dart'; import '../models/auth/logout_request.dart'; import '../models/auth/refresh_token_request.dart'; import '../models/auth/token_response.dart'; /// 인증 Repository 구현체 /// JWT 토큰 기반 인증 시스템을 관리하며 SharedPreferences를 사용해 토큰을 저장 @Injectable(as: AuthRepository) class AuthRepositoryImpl implements AuthRepository { final AuthRemoteDataSource remoteDataSource; final SharedPreferences sharedPreferences; // SharedPreferences 키 상수 static const String _keyAccessToken = 'access_token'; static const String _keyRefreshToken = 'refresh_token'; static const String _keyUserData = 'user_data'; AuthRepositoryImpl({ required this.remoteDataSource, required this.sharedPreferences, }); @override Future> login(LoginRequest loginRequest) async { try { final result = await remoteDataSource.login(loginRequest); return result.fold( (failure) => Left(failure), (loginResponse) async { // 로그인 성공 시 토큰과 사용자 정보를 로컬에 저장 await _saveTokens(loginResponse.accessToken, loginResponse.refreshToken); await _saveUserData(loginResponse.user); return Right(loginResponse); }, ); } catch (e) { return Left(ServerFailure( message: '로그인 처리 중 오류가 발생했습니다: ${e.toString()}', )); } } @override Future> logout() async { try { // 로컬에 저장된 리프레시 토큰으로 로그아웃 요청 생성 final refreshToken = await _getRefreshToken(); if (refreshToken == null) { // 토큰이 없으면 로컬 데이터만 삭제하고 성공 처리 await _clearLocalData(); return const Right(null); } final logoutRequest = LogoutRequest(refreshToken: refreshToken); final result = await remoteDataSource.logout(logoutRequest); return result.fold( (failure) async { // 서버 로그아웃 실패해도 로컬 데이터는 삭제 await _clearLocalData(); return Left(failure); }, (_) async { // 성공 시 로컬 데이터 삭제 await _clearLocalData(); return const Right(null); }, ); } catch (e) { // 오류 발생해도 로컬 데이터는 삭제 await _clearLocalData(); return Left(ServerFailure( message: '로그아웃 처리 중 오류가 발생했습니다: ${e.toString()}', )); } } @override Future> refreshToken(RefreshTokenRequest refreshRequest) async { try { final result = await remoteDataSource.refreshToken(refreshRequest); return result.fold( (failure) => Left(failure), (tokenResponse) async { // 새 토큰 저장 await _saveTokens(tokenResponse.accessToken, tokenResponse.refreshToken); return Right(tokenResponse); }, ); } catch (e) { return Left(ServerFailure( message: '토큰 갱신 중 오류가 발생했습니다: ${e.toString()}', )); } } @override Future> getCurrentUser() async { try { final userData = sharedPreferences.getString(_keyUserData); if (userData == null) { return const Left(AuthenticationFailure( message: '저장된 사용자 정보가 없습니다.', )); } // JSON 문자열을 AuthUser 객체로 변환 final user = AuthUser.fromJson( Map.from( // JSON 디코딩 처리 필요 시 여기에 추가 {} // TODO: JSON 디코딩 로직 추가 ) ); return Right(user); } catch (e) { return Left(ServerFailure( message: '사용자 정보 조회 중 오류가 발생했습니다: ${e.toString()}', )); } } @override Future> isAuthenticated() async { try { final accessToken = await _getAccessToken(); final refreshToken = await _getRefreshToken(); // 액세스 토큰과 리프레시 토큰이 모두 있으면 인증된 것으로 간주 final isAuth = accessToken != null && refreshToken != null; return Right(isAuth); } catch (e) { return Left(ServerFailure( message: '인증 상태 확인 중 오류가 발생했습니다: ${e.toString()}', )); } } @override Future> changePassword(String currentPassword, String newPassword) async { // TODO: 비밀번호 변경 API가 구현되면 추가 return const Left(ServerFailure( message: '비밀번호 변경 기능은 아직 구현되지 않았습니다.', )); } @override Future> requestPasswordReset(String email) async { // TODO: 비밀번호 재설정 API가 구현되면 추가 return const Left(ServerFailure( message: '비밀번호 재설정 기능은 아직 구현되지 않았습니다.', )); } @override Future> validateSession() async { try { final accessToken = await _getAccessToken(); if (accessToken == null) { return const Right(false); } // TODO: 서버에서 세션 유효성 검증 API가 있으면 호출 // 현재는 토큰 존재 여부만 확인 return const Right(true); } catch (e) { return Left(ServerFailure( message: '세션 유효성 검증 중 오류가 발생했습니다: ${e.toString()}', )); } } @override Future> getStoredRefreshToken() async { try { final token = await _getRefreshToken(); return Right(token); } catch (e) { return Left(ServerFailure( message: '리프레시 토큰 조회 중 오류가 발생했습니다: ${e.toString()}', )); } } @override Future> getStoredAccessToken() async { try { final token = await _getAccessToken(); return Right(token); } catch (e) { return Left(ServerFailure( message: '액세스 토큰 조회 중 오류가 발생했습니다: ${e.toString()}', )); } } @override Future> clearLocalSession() async { try { await _clearLocalData(); return const Right(null); } catch (e) { return Left(ServerFailure( message: '로컬 세션 정리 중 오류가 발생했습니다: ${e.toString()}', )); } } // Private 헬퍼 메서드들 /// 액세스 토큰과 리프레시 토큰을 로컬에 저장 Future _saveTokens(String accessToken, String refreshToken) async { await sharedPreferences.setString(_keyAccessToken, accessToken); await sharedPreferences.setString(_keyRefreshToken, refreshToken); } /// 사용자 데이터를 로컬에 저장 Future _saveUserData(AuthUser user) async { // TODO: JSON 인코딩 로직 구현 await sharedPreferences.setString(_keyUserData, user.toJson().toString()); } /// 액세스 토큰 조회 Future _getAccessToken() async { return sharedPreferences.getString(_keyAccessToken); } /// 리프레시 토큰 조회 Future _getRefreshToken() async { return sharedPreferences.getString(_keyRefreshToken); } /// 로컬 데이터 전체 삭제 Future _clearLocalData() async { await sharedPreferences.remove(_keyAccessToken); await sharedPreferences.remove(_keyRefreshToken); await sharedPreferences.remove(_keyUserData); } }