refactor: Clean Architecture 적용 및 코드베이스 전면 리팩토링
## 주요 변경사항 ### 아키텍처 개선 - Clean Architecture 패턴 적용 (Domain, Data, Presentation 레이어 분리) - Use Case 패턴 도입으로 비즈니스 로직 캡슐화 - Repository 패턴으로 데이터 접근 추상화 - 의존성 주입 구조 개선 ### 상태 관리 최적화 - 모든 Controller에서 불필요한 상태 관리 로직 제거 - 페이지네이션 로직 통일 및 간소화 - 에러 처리 로직 개선 (에러 메시지 한글화) - 로딩 상태 관리 최적화 ### Mock 서비스 제거 - MockDataService 완전 제거 - 모든 화면을 실제 API 전용으로 전환 - 불필요한 Mock 관련 코드 정리 ### UI/UX 개선 - Overview 화면 대시보드 기능 강화 - 라이선스 만료 알림 위젯 추가 - 사이드바 네비게이션 개선 - 일관된 UI 컴포넌트 사용 ### 코드 품질 - 중복 코드 제거 및 함수 추출 - 파일별 책임 분리 명확화 - 테스트 코드 업데이트 ## 영향 범위 - 모든 화면의 Controller 리팩토링 - API 통신 레이어 구조 개선 - 에러 처리 및 로깅 시스템 개선 ## 향후 계획 - 단위 테스트 커버리지 확대 - 통합 테스트 시나리오 추가 - 성능 모니터링 도구 통합
This commit is contained in:
167
lib/core/widgets/auth_guard.dart
Normal file
167
lib/core/widgets/auth_guard.dart
Normal file
@@ -0,0 +1,167 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../../services/auth_service.dart';
|
||||
import '../../data/models/auth/auth_user.dart';
|
||||
|
||||
/// 역할 기반 접근 제어를 위한 AuthGuard 위젯
|
||||
///
|
||||
/// 사용자의 역할을 확인하여 허용된 역할만 child 위젯에 접근할 수 있도록 제어합니다.
|
||||
/// 권한이 없는 경우 UnauthorizedScreen을 표시합니다.
|
||||
class AuthGuard extends StatelessWidget {
|
||||
/// 접근을 허용할 역할 목록
|
||||
final List<String> allowedRoles;
|
||||
|
||||
/// 권한이 있을 때 표시할 위젯
|
||||
final Widget child;
|
||||
|
||||
/// 권한이 없을 때 표시할 커스텀 위젯 (선택사항)
|
||||
final Widget? unauthorizedWidget;
|
||||
|
||||
/// 권한 체크를 건너뛸지 여부 (개발 모드용)
|
||||
final bool skipCheck;
|
||||
|
||||
const AuthGuard({
|
||||
super.key,
|
||||
required this.allowedRoles,
|
||||
required this.child,
|
||||
this.unauthorizedWidget,
|
||||
this.skipCheck = false,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// 개발 모드에서 권한 체크 건너뛰기
|
||||
if (skipCheck) {
|
||||
return child;
|
||||
}
|
||||
|
||||
// AuthService에서 현재 사용자 정보 가져오기
|
||||
final authService = context.read<AuthService>();
|
||||
|
||||
return FutureBuilder<AuthUser?>(
|
||||
future: authService.getCurrentUser(),
|
||||
builder: (context, snapshot) {
|
||||
// 로딩 중
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
// 현재 사용자 정보
|
||||
final currentUser = snapshot.data;
|
||||
|
||||
// 로그인하지 않은 경우
|
||||
if (currentUser == null) {
|
||||
return unauthorizedWidget ?? const UnauthorizedScreen(
|
||||
message: '로그인이 필요합니다.',
|
||||
);
|
||||
}
|
||||
|
||||
// 역할 확인 - 대소문자 구분 없이 비교
|
||||
final userRole = currentUser.role.toLowerCase();
|
||||
final hasPermission = allowedRoles.any(
|
||||
(role) => role.toLowerCase() == userRole,
|
||||
);
|
||||
|
||||
// 권한이 있는 경우
|
||||
if (hasPermission) {
|
||||
return child;
|
||||
}
|
||||
|
||||
// 권한이 없는 경우
|
||||
return unauthorizedWidget ?? UnauthorizedScreen(
|
||||
message: '이 페이지에 접근할 권한이 없습니다.',
|
||||
userRole: currentUser.role,
|
||||
requiredRoles: allowedRoles,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 권한이 없을 때 표시되는 기본 화면
|
||||
class UnauthorizedScreen extends StatelessWidget {
|
||||
final String message;
|
||||
final String? userRole;
|
||||
final List<String>? requiredRoles;
|
||||
|
||||
const UnauthorizedScreen({
|
||||
super.key,
|
||||
required this.message,
|
||||
this.userRole,
|
||||
this.requiredRoles,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Center(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(32),
|
||||
constraints: const BoxConstraints(maxWidth: 400),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.lock_outline,
|
||||
size: 80,
|
||||
color: Colors.grey,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Text(
|
||||
'접근 권한 없음',
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
message,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
if (userRole != null) ...[
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'현재 권한: $userRole',
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
],
|
||||
if (requiredRoles != null && requiredRoles!.isNotEmpty) ...[
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'필요한 권한: ${requiredRoles!.join(", ")}',
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 32),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pushReplacementNamed('/dashboard');
|
||||
},
|
||||
child: const Text('대시보드로 이동'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 역할 상수 정의
|
||||
class UserRole {
|
||||
static const String admin = 'Admin';
|
||||
static const String manager = 'Manager';
|
||||
static const String member = 'Member';
|
||||
|
||||
/// 관리자 및 매니저 권한
|
||||
static const List<String> adminAndManager = [admin, manager];
|
||||
|
||||
/// 모든 권한
|
||||
static const List<String> all = [admin, manager, member];
|
||||
|
||||
/// 관리자 전용
|
||||
static const List<String> adminOnly = [admin];
|
||||
}
|
||||
Reference in New Issue
Block a user