- CLAUDE.md 대폭 개선: 개발 가이드라인 및 프로젝트 상태 문서화 - 백엔드 API 통합: 모든 엔티티 간 Foreign Key 관계 완벽 구현 - UI 일관성 강화: shadcn_ui 컴포넌트 표준화 적용 - 데이터 모델 개선: DTO 및 모델 클래스 백엔드 스키마와 100% 일치 - 사용자 관리: 회사 연결, 중복 검사, 입력 검증 기능 추가 - 창고 관리: 우편번호 연결, 중복 검사 기능 강화 - 회사 관리: 우편번호 연결, 중복 검사 로직 구현 - 장비 관리: 불필요한 카테고리 필드 제거, 벤더-모델 관계 정리 - 우편번호 시스템: 검색 다이얼로그 Provider 버그 수정 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
192 lines
5.8 KiB
Dart
192 lines
5.8 KiB
Dart
import 'package:dio/dio.dart';
|
|
import 'package:injectable/injectable.dart';
|
|
import 'package:superport/core/errors/exceptions.dart';
|
|
import 'package:superport/data/datasources/remote/api_client.dart';
|
|
import 'package:superport/data/models/user/user_dto.dart';
|
|
|
|
/// 사용자 원격 데이터 소스 (서버 API v0.2.1 대응)
|
|
/// 엔드포인트: /api/v1/users
|
|
abstract class UserRemoteDataSource {
|
|
/// 사용자 목록 조회 (페이지네이션 지원)
|
|
Future<UserListResponse> getUsers({
|
|
int page = 1,
|
|
int perPage = 20,
|
|
bool? isActive,
|
|
String? role,
|
|
});
|
|
|
|
/// 단일 사용자 조회
|
|
Future<UserDto> getUser(int id);
|
|
|
|
/// 사용자 생성
|
|
Future<UserDto> createUser(UserRequestDto request);
|
|
|
|
/// 사용자 정보 수정 (비밀번호 포함)
|
|
Future<UserDto> updateUser(int id, UserUpdateRequestDto request);
|
|
|
|
/// 사용자 소프트 삭제 (is_active = false)
|
|
Future<void> deleteUser(int id);
|
|
|
|
}
|
|
|
|
@LazySingleton(as: UserRemoteDataSource)
|
|
class UserRemoteDataSourceImpl implements UserRemoteDataSource {
|
|
final ApiClient _apiClient;
|
|
|
|
UserRemoteDataSourceImpl(this._apiClient);
|
|
|
|
/// 사용자 목록 조회 (실제 백엔드 API 응답 대응)
|
|
@override
|
|
Future<UserListResponse> getUsers({
|
|
int page = 1,
|
|
int perPage = 20,
|
|
bool? isActive,
|
|
String? role,
|
|
}) async {
|
|
try {
|
|
final queryParams = <String, dynamic>{
|
|
'page': page,
|
|
'per_page': perPage,
|
|
};
|
|
|
|
// UI 호환 파라미터 (백엔드에서 무시)
|
|
if (isActive != null) {
|
|
queryParams['is_active'] = isActive;
|
|
}
|
|
if (role != null) {
|
|
queryParams['role'] = role;
|
|
}
|
|
|
|
final response = await _apiClient.get(
|
|
'/users',
|
|
queryParameters: queryParams,
|
|
);
|
|
|
|
// 실제 백엔드 응답 구조: {data: [...], total: 100, page: 1, page_size: 20, total_pages: 5}
|
|
if (response.data != null) {
|
|
return UserListResponse.fromJson(Map<String, dynamic>.from(response.data));
|
|
} else {
|
|
throw ApiException(
|
|
message: '사용자 목록을 불러오는데 실패했습니다',
|
|
statusCode: response.statusCode,
|
|
);
|
|
}
|
|
} on DioException catch (e) {
|
|
throw ApiException(
|
|
message: e.response?.data?['message'] ??
|
|
e.message ??
|
|
'사용자 목록을 불러오는데 실패했습니다',
|
|
statusCode: e.response?.statusCode,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 단일 사용자 조회 (GET /api/v1/users/{id})
|
|
@override
|
|
Future<UserDto> getUser(int id) async {
|
|
try {
|
|
final response = await _apiClient.get('/users/$id');
|
|
|
|
if (response.data != null) {
|
|
// 실제 백엔드 응답이 단일 객체인 경우
|
|
return UserDto.fromJson(Map<String, dynamic>.from(response.data));
|
|
} else {
|
|
throw ApiException(
|
|
message: '사용자 정보를 불러오는데 실패했습니다',
|
|
statusCode: response.statusCode,
|
|
);
|
|
}
|
|
} on DioException catch (e) {
|
|
throw ApiException(
|
|
message: e.response?.data?['message'] ??
|
|
e.message ??
|
|
'사용자 정보를 불러오는데 실패했습니다',
|
|
statusCode: e.response?.statusCode,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 사용자 생성 (POST /api/v1/users)
|
|
@override
|
|
Future<UserDto> createUser(UserRequestDto request) async {
|
|
try {
|
|
final response = await _apiClient.post(
|
|
'/users',
|
|
data: request.toJson(),
|
|
);
|
|
|
|
if (response.data != null) {
|
|
return UserDto.fromJson(Map<String, dynamic>.from(response.data));
|
|
} else {
|
|
throw ApiException(
|
|
message: '사용자 생성에 실패했습니다',
|
|
statusCode: response.statusCode,
|
|
);
|
|
}
|
|
} on DioException catch (e) {
|
|
throw ApiException(
|
|
message: e.response?.data?['message'] ??
|
|
e.message ??
|
|
'사용자 생성에 실패했습니다',
|
|
statusCode: e.response?.statusCode,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 사용자 수정 (PUT /api/v1/users/{id})
|
|
@override
|
|
Future<UserDto> updateUser(int id, UserUpdateRequestDto request) async {
|
|
try {
|
|
// null이나 빈 값 필터링하여 실제로 변경된 필드만 전송
|
|
final requestData = request.toJson();
|
|
requestData.removeWhere((key, value) => value == null ||
|
|
(value is String && value.isEmpty));
|
|
|
|
final response = await _apiClient.put(
|
|
'/users/$id',
|
|
data: requestData,
|
|
);
|
|
|
|
if (response.data != null) {
|
|
return UserDto.fromJson(Map<String, dynamic>.from(response.data));
|
|
} else {
|
|
throw ApiException(
|
|
message: '사용자 정보 수정에 실패했습니다',
|
|
statusCode: response.statusCode,
|
|
);
|
|
}
|
|
} on DioException catch (e) {
|
|
throw ApiException(
|
|
message: e.response?.data?['message'] ??
|
|
e.message ??
|
|
'사용자 정보 수정에 실패했습니다',
|
|
statusCode: e.response?.statusCode,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 사용자 소프트 삭제 (DELETE /api/v1/users/{id})
|
|
/// 서버에서 is_active = false로 설정
|
|
@override
|
|
Future<void> deleteUser(int id) async {
|
|
try {
|
|
final response = await _apiClient.delete('/users/$id');
|
|
|
|
// 소프트 딜리트는 응답 데이터가 있을 수 있음
|
|
if (response.statusCode != null && response.statusCode! >= 400) {
|
|
throw ApiException(
|
|
message: response.data?['message'] ?? '사용자 삭제에 실패했습니다',
|
|
statusCode: response.statusCode,
|
|
);
|
|
}
|
|
} on DioException catch (e) {
|
|
throw ApiException(
|
|
message: e.response?.data?['message'] ??
|
|
e.message ??
|
|
'사용자 삭제에 실패했습니다',
|
|
statusCode: e.response?.statusCode,
|
|
);
|
|
}
|
|
}
|
|
|
|
} |