Files
superport/lib/domain/usecases/administrator_usecase.dart

328 lines
9.5 KiB
Dart

import 'package:dartz/dartz.dart';
import 'package:injectable/injectable.dart';
import 'package:superport/core/errors/failures.dart';
import 'package:superport/data/models/administrator_dto.dart';
import 'package:superport/domain/repositories/administrator_repository.dart';
import 'package:superport/utils/constants.dart';
/// 관리자 UseCase 인터페이스 (비즈니스 로직)
abstract class AdministratorUseCase {
Future<Either<Failure, AdministratorListResponse>> getAdministrators({
int page = 1,
int pageSize = PaginationConstants.defaultPageSize,
String? search,
});
Future<Either<Failure, AdministratorDto>> getAdministratorById(int id);
Future<Either<Failure, AdministratorDto>> createAdministrator(
AdministratorRequestDto administrator,
);
Future<Either<Failure, AdministratorDto>> updateAdministrator(
int id,
AdministratorUpdateRequestDto administrator,
);
Future<Either<Failure, void>> deleteAdministrator(int id);
Future<Either<Failure, bool>> checkEmailDuplicate(String email, {int? excludeId});
Future<Either<Failure, AdministratorDto>> authenticateAdministrator(
String email,
String password,
);
Future<Either<Failure, bool>> validateAdministratorData(
String name,
String email,
String phone,
String mobile,
);
}
/// 관리자 UseCase 구현체
@Injectable(as: AdministratorUseCase)
class AdministratorUseCaseImpl implements AdministratorUseCase {
final AdministratorRepository _repository;
AdministratorUseCaseImpl(this._repository);
@override
Future<Either<Failure, AdministratorListResponse>> getAdministrators({
int page = 1,
int pageSize = PaginationConstants.defaultPageSize,
String? search,
}) async {
// 비즈니스 로직: 페이지네이션 유효성 검사
if (page < 1) page = 1;
if (pageSize < 1 || pageSize > 100) pageSize = 20;
return await _repository.getAdministrators(
page: page,
pageSize: pageSize,
search: search,
);
}
@override
Future<Either<Failure, AdministratorDto>> getAdministratorById(int id) async {
// 비즈니스 규칙: ID 유효성 검사
if (id <= 0) {
return Left(ValidationFailure(
message: '유효하지 않은 관리자 ID입니다.',
));
}
return await _repository.getAdministratorById(id);
}
@override
Future<Either<Failure, AdministratorDto>> createAdministrator(
AdministratorRequestDto administrator,
) async {
// 비즈니스 규칙 검증
final validationResult = await validateAdministratorData(
administrator.name,
administrator.email,
administrator.phone,
administrator.mobile,
);
return validationResult.fold(
(failure) => Left(failure),
(isValid) async {
if (!isValid) {
return Left(ValidationFailure(
message: '관리자 정보가 유효하지 않습니다.',
));
}
// 이메일 중복 검사
final duplicateResult = await checkEmailDuplicate(administrator.email);
return duplicateResult.fold(
(failure) => Left(failure),
(isDuplicate) async {
if (isDuplicate) {
return Left(DuplicateFailure(
message: '이미 사용 중인 이메일 주소입니다.',
));
}
return await _repository.createAdministrator(administrator);
},
);
},
);
}
@override
Future<Either<Failure, AdministratorDto>> updateAdministrator(
int id,
AdministratorUpdateRequestDto administrator,
) async {
// 비즈니스 규칙: ID 유효성 검사
if (id <= 0) {
return Left(ValidationFailure(
message: '유효하지 않은 관리자 ID입니다.',
));
}
// 수정할 데이터가 있는 경우에만 검증
if (administrator.name != null ||
administrator.email != null ||
administrator.phone != null ||
administrator.mobile != null) {
// 현재 데이터 조회 (검증용)
final currentResult = await _repository.getAdministratorById(id);
return currentResult.fold(
(failure) => Left(failure),
(current) async {
// 새로운 값들로 검증
final newName = administrator.name ?? current.name;
final newEmail = administrator.email ?? current.email;
final newPhone = administrator.phone ?? current.phone;
final newMobile = administrator.mobile ?? current.mobile;
final validationResult = await validateAdministratorData(
newName,
newEmail,
newPhone,
newMobile,
);
return validationResult.fold(
(failure) => Left(failure),
(isValid) async {
if (!isValid) {
return Left(ValidationFailure(
message: '관리자 정보가 유효하지 않습니다.',
));
}
// 이메일이 변경된 경우 중복 검사
if (administrator.email != null && administrator.email != current.email) {
final duplicateResult = await checkEmailDuplicate(
administrator.email!,
excludeId: id,
);
return duplicateResult.fold(
(failure) => Left(failure),
(isDuplicate) async {
if (isDuplicate) {
return Left(DuplicateFailure(
message: '이미 사용 중인 이메일 주소입니다.',
));
}
return await _repository.updateAdministrator(id, administrator);
},
);
}
return await _repository.updateAdministrator(id, administrator);
},
);
},
);
}
return await _repository.updateAdministrator(id, administrator);
}
@override
Future<Either<Failure, void>> deleteAdministrator(int id) async {
// 비즈니스 규칙: ID 유효성 검사
if (id <= 0) {
return Left(ValidationFailure(
message: '유효하지 않은 관리자 ID입니다.',
));
}
// 비즈니스 로직: 관리자가 최소 1명은 남아있어야 함
final administratorsResult = await _repository.getAdministrators(
page: 1,
pageSize: 5,
);
return administratorsResult.fold(
(failure) => Left(failure),
(administrators) async {
if (administrators.totalCount <= 1) {
return Left(ValidationFailure(
message: '최소 1명의 관리자가 존재해야 합니다.',
));
}
return await _repository.deleteAdministrator(id);
},
);
}
@override
Future<Either<Failure, bool>> checkEmailDuplicate(String email, {int? excludeId}) async {
// 이메일 형식 검증
if (!_isValidEmail(email)) {
return Left(ValidationFailure(
message: '유효하지 않은 이메일 형식입니다.',
));
}
return await _repository.isDuplicateEmail(email, excludeId: excludeId);
}
@override
Future<Either<Failure, AdministratorDto>> authenticateAdministrator(
String email,
String password,
) async {
// 입력값 검증
if (email.trim().isEmpty || password.isEmpty) {
return Left(ValidationFailure(
message: '이메일과 비밀번호를 입력해주세요.',
));
}
if (!_isValidEmail(email)) {
return Left(ValidationFailure(
message: '유효하지 않은 이메일 형식입니다.',
));
}
return await _repository.authenticateAdministrator(email, password);
}
@override
Future<Either<Failure, bool>> validateAdministratorData(
String name,
String email,
String phone,
String mobile,
) async {
// 필수 필드 검증
if (name.trim().isEmpty) {
return Left(ValidationFailure(
message: '관리자 이름을 입력해주세요.',
));
}
if (email.trim().isEmpty) {
return Left(ValidationFailure(
message: '이메일 주소를 입력해주세요.',
));
}
if (phone.trim().isEmpty) {
return Left(ValidationFailure(
message: '전화번호를 입력해주세요.',
));
}
if (mobile.trim().isEmpty) {
return Left(ValidationFailure(
message: '휴대폰 번호를 입력해주세요.',
));
}
// 이메일 형식 검증
if (!_isValidEmail(email)) {
return Left(ValidationFailure(
message: '유효하지 않은 이메일 형식입니다.',
));
}
// 이름 길이 검증
if (name.length > 100) {
return Left(ValidationFailure(
message: '관리자 이름은 100자 이내로 입력해주세요.',
));
}
// 전화번호 형식 검증 (숫자, 하이픈, 공백만 허용)
if (!_isValidPhoneNumber(phone)) {
return Left(ValidationFailure(
message: '유효하지 않은 전화번호 형식입니다.',
));
}
if (!_isValidPhoneNumber(mobile)) {
return Left(ValidationFailure(
message: '유효하지 않은 휴대폰 번호 형식입니다.',
));
}
return const Right(true);
}
/// 이메일 형식 검증 (간단한 정규식)
bool _isValidEmail(String email) {
return RegExp(r'^[^\s@]+@[^\s@]+\.[^\s@]+$').hasMatch(email.trim());
}
/// 전화번호 형식 검증 (한국 형식 기준)
bool _isValidPhoneNumber(String phone) {
final cleaned = phone.replaceAll(RegExp(r'[\s\-\(\)]'), '');
return RegExp(r'^\d{8,11}$').hasMatch(cleaned);
}
}