feat: API 통합을 위한 기초 인프라 구축

- 네트워크 레이어 구현 (Dio 기반 ApiClient)
- 환경별 설정 관리 시스템 구축
- 의존성 주입 설정 (GetIt)
- API 엔드포인트 상수 정의
- 인터셉터 구현 (Auth, Error, Logging)
- 프로젝트 아키텍처 개선 (core, data, di 디렉토리 구조)
- API 통합 계획서 및 요구사항 문서 작성
- 필요 패키지 추가 (dio, flutter_secure_storage, get_it 등)
This commit is contained in:
JiWoong Sul
2025-07-24 14:54:28 +09:00
parent e0bc5894b2
commit 2b31d3af5f
29 changed files with 3542 additions and 344 deletions

View File

@@ -0,0 +1,154 @@
import '../constants/app_constants.dart';
/// 유효성 검사 유틸리티 클래스
class Validators {
/// 이메일 유효성 검사
static String? validateEmail(String? value) {
if (value == null || value.isEmpty) {
return '이메일을 입력해주세요.';
}
if (!AppConstants.emailRegex.hasMatch(value)) {
return '올바른 이메일 형식이 아닙니다.';
}
return null;
}
/// 비밀번호 유효성 검사
static String? validatePassword(String? value) {
if (value == null || value.isEmpty) {
return '비밀번호를 입력해주세요.';
}
if (value.length < 8) {
return '비밀번호는 8자 이상이어야 합니다.';
}
if (!value.contains(RegExp(r'[0-9]'))) {
return '비밀번호는 숫자를 포함해야 합니다.';
}
if (!value.contains(RegExp(r'[a-zA-Z]'))) {
return '비밀번호는 영문자를 포함해야 합니다.';
}
return null;
}
/// 필수 입력 검사
static String? validateRequired(String? value, String fieldName) {
if (value == null || value.trim().isEmpty) {
return '$fieldName을(를) 입력해주세요.';
}
return null;
}
/// 전화번호 유효성 검사
static String? validatePhone(String? value) {
if (value == null || value.isEmpty) {
return '전화번호를 입력해주세요.';
}
final cleanedValue = value.replaceAll('-', '');
if (!AppConstants.phoneRegex.hasMatch(cleanedValue)) {
return '올바른 전화번호 형식이 아닙니다.';
}
return null;
}
/// 사업자번호 유효성 검사
static String? validateBusinessNumber(String? value) {
if (value == null || value.isEmpty) {
return '사업자번호를 입력해주세요.';
}
final cleanedValue = value.replaceAll('-', '');
if (!AppConstants.businessNumberRegex.hasMatch(cleanedValue)) {
return '올바른 사업자번호 형식이 아닙니다.';
}
return null;
}
/// 숫자 유효성 검사
static String? validateNumber(String? value, {
required String fieldName,
int? min,
int? max,
}) {
if (value == null || value.isEmpty) {
return '$fieldName을(를) 입력해주세요.';
}
final number = int.tryParse(value);
if (number == null) {
return '숫자만 입력 가능합니다.';
}
if (min != null && number < min) {
return '$fieldName은(는) $min 이상이어야 합니다.';
}
if (max != null && number > max) {
return '$fieldName은(는) $max 이하여야 합니다.';
}
return null;
}
/// 날짜 범위 유효성 검사
static String? validateDateRange(
DateTime? startDate,
DateTime? endDate,
) {
if (startDate == null || endDate == null) {
return null;
}
if (startDate.isAfter(endDate)) {
return '시작일은 종료일보다 이전이어야 합니다.';
}
return null;
}
/// 파일 확장자 유효성 검사
static String? validateFileExtension(String fileName) {
final extension = fileName.split('.').last.toLowerCase();
if (!AppConstants.allowedFileExtensions.contains(extension)) {
return '허용되지 않는 파일 형식입니다. (${AppConstants.allowedFileExtensions.join(', ')})';
}
return null;
}
/// 파일 크기 유효성 검사
static String? validateFileSize(int sizeInBytes) {
if (sizeInBytes > AppConstants.maxFileSize) {
final maxSizeMB = AppConstants.maxFileSize / (1024 * 1024);
return '파일 크기는 ${maxSizeMB}MB를 초과할 수 없습니다.';
}
return null;
}
/// 시리얼 번호 유효성 검사
static String? validateSerialNumber(String? value) {
if (value == null || value.isEmpty) {
return null; // 시리얼 번호는 선택사항
}
if (value.length < 5) {
return '시리얼 번호는 5자 이상이어야 합니다.';
}
if (!RegExp(r'^[A-Za-z0-9-]+$').hasMatch(value)) {
return '시리얼 번호는 영문, 숫자, 하이픈(-)만 사용 가능합니다.';
}
return null;
}
}