328 lines
9.5 KiB
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);
|
|
}
|
|
} |