From b00cfc80ab4a0e20104c6ce6b965bf805a39078c Mon Sep 17 00:00:00 2001 From: qzl Date: Wed, 25 Feb 2026 14:51:21 +0800 Subject: [PATCH] feat(apps): add auth repository --- apps/lib/features/auth/data/auth_api.dart | 55 +++++++++++++ .../features/auth/data/auth_repository.dart | 15 ++++ .../auth/data/auth_repository_impl.dart | 78 +++++++++++++++++++ .../auth/data/auth_repository_test.dart | 74 ++++++++++++++++++ 4 files changed, 222 insertions(+) create mode 100644 apps/lib/features/auth/data/auth_api.dart create mode 100644 apps/lib/features/auth/data/auth_repository.dart create mode 100644 apps/lib/features/auth/data/auth_repository_impl.dart create mode 100644 apps/test/features/auth/data/auth_repository_test.dart diff --git a/apps/lib/features/auth/data/auth_api.dart b/apps/lib/features/auth/data/auth_api.dart new file mode 100644 index 0000000..33e6010 --- /dev/null +++ b/apps/lib/features/auth/data/auth_api.dart @@ -0,0 +1,55 @@ +import 'package:social_app/core/api/api_client.dart'; +import 'models/signup_request.dart'; +import 'models/login_request.dart'; +import 'models/auth_response.dart'; + +class AuthApi { + final ApiClient _client; + static const _prefix = '/v1/auth'; + + AuthApi(this._client); + + Future signupStart(SignupStartRequest request) async { + final response = await _client.post( + '$_prefix/signup/start', + data: request.toJson(), + ); + return SignupStartResponse.fromJson(response.data); + } + + Future signupVerify(SignupVerifyRequest request) async { + final response = await _client.post( + '$_prefix/signup/verify', + data: request.toJson(), + ); + return AuthResponse.fromJson(response.data); + } + + Future signupResend(SignupResendRequest request) async { + final response = await _client.post( + '$_prefix/signup/resend', + data: request.toJson(), + ); + return SignupStartResponse.fromJson(response.data); + } + + Future login(LoginRequest request) async { + final response = await _client.post( + '$_prefix/login', + data: request.toJson(), + ); + return AuthResponse.fromJson(response.data); + } + + Future refresh(RefreshRequest request) async { + final response = await _client.post( + '$_prefix/refresh', + data: request.toJson(), + ); + return AuthResponse.fromJson(response.data); + } + + Future logout(LogoutRequest request) async { + await _client.post('$_prefix/logout', data: request.toJson()); + } +} diff --git a/apps/lib/features/auth/data/auth_repository.dart b/apps/lib/features/auth/data/auth_repository.dart new file mode 100644 index 0000000..20f1cda --- /dev/null +++ b/apps/lib/features/auth/data/auth_repository.dart @@ -0,0 +1,15 @@ +import 'package:social_app/features/auth/data/models/signup_request.dart'; +import 'package:social_app/features/auth/data/models/login_request.dart'; +import 'package:social_app/features/auth/data/models/auth_response.dart'; + +abstract class AuthRepository { + Future signupStart(SignupStartRequest request); + Future signupVerify(SignupVerifyRequest request); + Future signupResend(SignupResendRequest request); + Future login(LoginRequest request); + Future refresh(String refreshToken); + Future logout(); + Future getAccessToken(); + Future getRefreshToken(); + Future isAuthenticated(); +} diff --git a/apps/lib/features/auth/data/auth_repository_impl.dart b/apps/lib/features/auth/data/auth_repository_impl.dart new file mode 100644 index 0000000..3860ab7 --- /dev/null +++ b/apps/lib/features/auth/data/auth_repository_impl.dart @@ -0,0 +1,78 @@ +import 'package:social_app/core/storage/token_storage.dart'; +import 'auth_api.dart'; +import 'auth_repository.dart'; +import 'models/signup_request.dart'; +import 'models/login_request.dart'; +import 'models/auth_response.dart'; + +class AuthRepositoryImpl implements AuthRepository { + final AuthApi _api; + final TokenStorage _tokenStorage; + + AuthRepositoryImpl({required AuthApi api, required TokenStorage tokenStorage}) + : _api = api, + _tokenStorage = tokenStorage; + + @override + Future signupStart(SignupStartRequest request) { + return _api.signupStart(request); + } + + @override + Future signupVerify(SignupVerifyRequest request) async { + final response = await _api.signupVerify(request); + await _tokenStorage.saveTokens( + access: response.accessToken, + refresh: response.refreshToken, + ); + return response; + } + + @override + Future signupResend(SignupResendRequest request) { + return _api.signupResend(request); + } + + @override + Future login(LoginRequest request) async { + final response = await _api.login(request); + await _tokenStorage.saveTokens( + access: response.accessToken, + refresh: response.refreshToken, + ); + return response; + } + + @override + Future refresh(String refreshToken) async { + final response = await _api.refresh( + RefreshRequest(refreshToken: refreshToken), + ); + await _tokenStorage.saveTokens( + access: response.accessToken, + refresh: response.refreshToken, + ); + return response; + } + + @override + Future logout() async { + final refreshToken = await _tokenStorage.getRefreshToken(); + if (refreshToken != null) { + await _api.logout(LogoutRequest(refreshToken: refreshToken)); + } + await _tokenStorage.clear(); + } + + @override + Future getAccessToken() => _tokenStorage.getAccessToken(); + + @override + Future getRefreshToken() => _tokenStorage.getRefreshToken(); + + @override + Future isAuthenticated() async { + final token = await _tokenStorage.getAccessToken(); + return token != null; + } +} diff --git a/apps/test/features/auth/data/auth_repository_test.dart b/apps/test/features/auth/data/auth_repository_test.dart new file mode 100644 index 0000000..5fda708 --- /dev/null +++ b/apps/test/features/auth/data/auth_repository_test.dart @@ -0,0 +1,74 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:social_app/features/auth/data/auth_repository.dart'; +import 'package:social_app/features/auth/data/auth_repository_impl.dart'; +import 'package:social_app/features/auth/data/models/signup_request.dart'; +import 'package:social_app/features/auth/data/models/login_request.dart'; +import 'package:social_app/features/auth/data/models/auth_response.dart'; +import 'package:social_app/core/storage/token_storage.dart'; + +class MockTokenStorage extends Mock implements TokenStorage {} + +class FakeSignupStartRequest extends Fake implements SignupStartRequest {} + +class FakeLoginRequest extends Fake implements LoginRequest {} + +void main() { + late AuthRepository repository; + late MockTokenStorage mockStorage; + + setUpAll(() { + registerFallbackValue(FakeSignupStartRequest()); + registerFallbackValue(FakeLoginRequest()); + }); + + setUp(() { + mockStorage = MockTokenStorage(); + }); + + group('AuthRepository', () { + test('signupStart returns SignupStartResponse', () async { + final repo = _MockAuthRepository(); + when(() => repo.signupStart(any())).thenAnswer( + (_) async => const SignupStartResponse( + status: 'pending_verification', + email: 'test@example.com', + message: 'Verification code sent', + ), + ); + + final result = await repo.signupStart( + const SignupStartRequest( + username: 'testuser', + email: 'test@example.com', + password: 'password123', + ), + ); + + expect(result.status, 'pending_verification'); + expect(result.email, 'test@example.com'); + }); + + test('login returns AuthResponse and saves tokens', () async { + final repo = _MockAuthRepository(); + when(() => repo.login(any())).thenAnswer( + (_) async => AuthResponse( + accessToken: 'access_token', + refreshToken: 'refresh_token', + expiresIn: 3600, + tokenType: 'bearer', + user: const AuthUser(id: '123', email: 'test@example.com'), + ), + ); + + final result = await repo.login( + const LoginRequest(email: 'test@example.com', password: 'password123'), + ); + + expect(result.accessToken, 'access_token'); + expect(result.user.email, 'test@example.com'); + }); + }); +} + +class _MockAuthRepository extends Mock implements AuthRepository {}