초기 커밋

This commit is contained in:
2026-03-01 07:55:59 +09:00
commit b0262d6bab
67 changed files with 4660 additions and 0 deletions

View File

@@ -0,0 +1,28 @@
import 'package:dio/dio.dart';
import 'package:retrofit/retrofit.dart';
import '../../../../core/constants/api_constants.dart';
import '../models/login_request.dart';
import '../models/register_request.dart';
import '../models/token_response.dart';
part 'auth_remote_source.g.dart';
@RestApi()
abstract class AuthRemoteSource {
factory AuthRemoteSource(Dio dio) = _AuthRemoteSource;
@POST(ApiConstants.login)
Future<TokenResponse> login(@Body() LoginRequest request);
@POST(ApiConstants.register)
Future<TokenResponse> register(@Body() RegisterRequest request);
@POST(ApiConstants.logout)
Future<void> logout();
@POST(ApiConstants.refreshToken)
Future<TokenResponse> refreshToken(
@Body() Map<String, String> body,
);
}

View File

@@ -0,0 +1,15 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'login_request.freezed.dart';
part 'login_request.g.dart';
@freezed
class LoginRequest with _$LoginRequest {
const factory LoginRequest({
required String email,
required String password,
}) = _LoginRequest;
factory LoginRequest.fromJson(Map<String, dynamic> json) =>
_$LoginRequestFromJson(json);
}

View File

@@ -0,0 +1,16 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'register_request.freezed.dart';
part 'register_request.g.dart';
@freezed
class RegisterRequest with _$RegisterRequest {
const factory RegisterRequest({
required String email,
required String password,
required String name,
}) = _RegisterRequest;
factory RegisterRequest.fromJson(Map<String, dynamic> json) =>
_$RegisterRequestFromJson(json);
}

View File

@@ -0,0 +1,16 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'token_response.freezed.dart';
part 'token_response.g.dart';
@freezed
class TokenResponse with _$TokenResponse {
const factory TokenResponse({
@JsonKey(name: 'access_token') required String accessToken,
@JsonKey(name: 'refresh_token') required String refreshToken,
@JsonKey(name: 'token_type') @Default('bearer') String tokenType,
}) = _TokenResponse;
factory TokenResponse.fromJson(Map<String, dynamic> json) =>
_$TokenResponseFromJson(json);
}

View File

@@ -0,0 +1,123 @@
import 'dart:convert';
import 'package:talker/talker.dart';
import '../../../../core/constants/app_constants.dart';
import '../../../../core/error/exceptions.dart';
import '../../../../core/storage/secure_storage.dart';
import '../../../../shared/models/user_role.dart';
import '../../domain/entities/user.dart';
import '../../domain/repositories/auth_repository.dart';
import '../datasources/auth_remote_source.dart';
import '../models/login_request.dart';
import '../models/register_request.dart';
class AuthRepositoryImpl implements AuthRepository {
AuthRepositoryImpl({
required this.remoteSource,
required this.secureStorage,
required this.talker,
});
final AuthRemoteSource remoteSource;
final SecureStorage secureStorage;
final Talker talker;
@override
Future<User> login({
required String email,
required String password,
}) async {
try {
final response = await remoteSource.login(
LoginRequest(email: email, password: password),
);
await _saveTokens(response.accessToken, response.refreshToken);
// 로그인 후 사용자 정보 조회
final user = await getCurrentUser();
if (user == null) {
throw const ServerException(
message: '사용자 정보를 가져올 수 없습니다',
statusCode: 500,
);
}
return user;
} catch (e) {
talker.error('Login failed', e);
rethrow;
}
}
@override
Future<User> register({
required String email,
required String password,
required String name,
}) async {
try {
final response = await remoteSource.register(
RegisterRequest(email: email, password: password, name: name),
);
await _saveTokens(response.accessToken, response.refreshToken);
return User(
id: '',
email: email,
name: name,
role: UserRole.user,
);
} catch (e) {
talker.error('Register failed', e);
rethrow;
}
}
@override
Future<void> logout() async {
try {
await remoteSource.logout();
} catch (_) {
// 서버 로그아웃 실패해도 로컬 토큰은 삭제
} finally {
await secureStorage.delete(key: AppConstants.accessTokenKey);
await secureStorage.delete(key: AppConstants.refreshTokenKey);
await secureStorage.delete(key: AppConstants.userKey);
}
}
@override
Future<User?> getCurrentUser() async {
try {
final userData = await secureStorage.read(key: AppConstants.userKey);
if (userData != null) {
return User.fromJson(
jsonDecode(userData) as Map<String, dynamic>,
);
}
return null;
} catch (e) {
talker.error('Get current user failed', e);
return null;
}
}
@override
Future<bool> isLoggedIn() async {
final token = await secureStorage.read(key: AppConstants.accessTokenKey);
return token != null;
}
Future<void> _saveTokens(String accessToken, String refreshToken) async {
await secureStorage.write(
key: AppConstants.accessTokenKey,
value: accessToken,
);
await secureStorage.write(
key: AppConstants.refreshTokenKey,
value: refreshToken,
);
}
}