docs: 템플릿 규칙 정비 및 환경변수 예제·README 추가
- CLAUDE.md: 모노레포 개요·표준 응답 포맷·에러 코드 맵(구현 일치)·환경변수 위임 구조(SSOT) 정리 - nestjs/vue3/flutter 규칙을 실제 구현(클래스명, env키, 디렉터리)과 정합화 - 루트 .env 단일 진실 공급원 구조에 맞춰 docker-compose 변수명 정정(BACKEND_API_URL, DB/REDIS_DATA_DIR) - backend/frontend/flutter 예제 env 추가(루트 위임 반영, 서비스 고유 값만 작성) - README.md 신규 작성, command.md 범용화, .gitignore에 *.example 재포함 규칙 추가 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -24,8 +24,8 @@ lib/
|
||||
├── core/
|
||||
│ ├── constants/ ← 상수 (색상, 크기, 텍스트 등)
|
||||
│ ├── theme/ ← 앱 테마 정의
|
||||
│ ├── network/ ← Dio 인스턴스, 인터셉터
|
||||
│ ├── storage/ ← flutter_secure_storage 래퍼
|
||||
│ ├── api/ ← Dio 인스턴스(dio_client), 에러 사전(error_lexicon), 인터셉터
|
||||
│ ├── storage/ ← flutter_secure_storage 래퍼 (선택)
|
||||
│ └── utils/ ← 순수 유틸 함수
|
||||
├── features/
|
||||
│ └── user/
|
||||
@@ -136,14 +136,16 @@ class UserWidget extends ConsumerWidget {
|
||||
## API 통신 (Dio + 인터셉터)
|
||||
|
||||
```dart
|
||||
// core/network/dio_client.dart
|
||||
// core/api/dio_client.dart
|
||||
class DioClient {
|
||||
late final Dio _dio;
|
||||
|
||||
DioClient() {
|
||||
_dio = Dio(BaseOptions(baseUrl: Env.apiUrl));
|
||||
// flutter_dotenv 로 .env 로드, 미설정 시 로컬 기본값
|
||||
final baseUrl = dotenv.maybeGet('API_URL') ?? 'http://localhost:3000/api';
|
||||
_dio = Dio(BaseOptions(baseUrl: baseUrl));
|
||||
_dio.interceptors.add(AuthInterceptor()); // 토큰 자동 첨부
|
||||
_dio.interceptors.add(ErrorInterceptor()); // 에러 코드 처리
|
||||
_dio.interceptors.add(ErrorInterceptor()); // 에러 코드 처리 (error_lexicon 참조)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,11 +166,11 @@ class ErrorInterceptor extends Interceptor {
|
||||
// flutter_secure_storage 사용
|
||||
const storage = FlutterSecureStorage();
|
||||
|
||||
// 저장
|
||||
await storage.write(key: 'access_token', value: token);
|
||||
// 저장 (키 이름은 dio_client 인터셉터와 통일: jwt_token)
|
||||
await storage.write(key: 'jwt_token', value: token);
|
||||
|
||||
// 읽기
|
||||
final token = await storage.read(key: 'access_token');
|
||||
final token = await storage.read(key: 'jwt_token');
|
||||
|
||||
// 삭제 (로그아웃)
|
||||
await storage.deleteAll();
|
||||
|
||||
@@ -74,21 +74,22 @@ export class CreateUserDto {
|
||||
## 표준 응답 인터셉터
|
||||
|
||||
```typescript
|
||||
// common/interceptors/response.interceptor.ts
|
||||
// common/interceptors/transform.interceptor.ts
|
||||
@Injectable()
|
||||
export class ResponseInterceptor implements NestInterceptor {
|
||||
export class TransformInterceptor<T> implements NestInterceptor {
|
||||
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
||||
return next.handle().pipe(
|
||||
map((data) => ({
|
||||
success: true,
|
||||
data,
|
||||
data: data ?? null,
|
||||
})),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `main.ts`에서 전역 등록: `app.useGlobalInterceptors(new ResponseInterceptor())`
|
||||
- `main.ts`에서 전역 등록: `app.useGlobalInterceptors(new TransformInterceptor())`
|
||||
- 실패 응답(`success: false`)은 아래 Exception Filter가 생성한다.
|
||||
|
||||
---
|
||||
|
||||
@@ -97,12 +98,12 @@ export class ResponseInterceptor implements NestInterceptor {
|
||||
```typescript
|
||||
// common/filters/http-exception.filter.ts
|
||||
@Catch()
|
||||
export class AllExceptionsFilter implements ExceptionFilter {
|
||||
export class HttpExceptionFilter implements ExceptionFilter {
|
||||
catch(exception: unknown, host: ArgumentsHost) {
|
||||
const ctx = host.switchToHttp();
|
||||
const response = ctx.getResponse<Response>();
|
||||
|
||||
// 에러 코드 매핑 로직 포함
|
||||
// HTTP 상태 → 에러 코드 매핑 후 { success: false, error: { code, message } } 반환
|
||||
// SYS_001 / AUTH_001 / VAL_001 등 CLAUDE.md 에러 맵 참조
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,13 +88,13 @@ export const useUserStore = defineStore('user', () => {
|
||||
```typescript
|
||||
// composables/useApi.ts — 중앙 Axios 인스턴스
|
||||
const api = axios.create({
|
||||
baseURL: import.meta.env.VITE_API_URL,
|
||||
baseURL: import.meta.env.VITE_API_BASE_URL || '/api', // env.d.ts 에 타입 선언
|
||||
withCredentials: true, // HttpOnly 쿠키 전송
|
||||
})
|
||||
|
||||
// 응답 인터셉터 — 에러 코드 전역 처리
|
||||
// 응답 인터셉터 — 표준 응답 언래핑 + 에러 코드 전역 처리
|
||||
api.interceptors.response.use(
|
||||
(response) => response.data.data, // success: true → data 언래핑
|
||||
(response) => response.data.data, // { success, data } → data 언래핑
|
||||
(error) => {
|
||||
const code = error.response?.data?.error?.code
|
||||
// 에러 코드별 Toast 알림 처리 (CLAUDE.md 에러 맵 참조)
|
||||
|
||||
Reference in New Issue
Block a user