first commit

This commit is contained in:
2026-05-18 11:25:51 +09:00
commit 4d70b1428a
197 changed files with 28388 additions and 0 deletions
+189
View File
@@ -0,0 +1,189 @@
---
description: Flutter 모바일 앱 개발 규칙
---
> 이 파일은 `.claude/commands/flutter.md`에 위치한다.
> 사용법: `/flutter [요청 내용]`
---
## 언어 & 기본 원칙
- **Dart Sound Null Safety** 완벽 준수 (`?`, `!`, `late` 올바르게 사용)
- `dynamic` 타입 사용 **지양** → 명시적 타입 선언
- 모든 주석은 **한국어**로 작성
---
## 프로젝트 구조
```
lib/
├── main.dart
├── app.dart ← MaterialApp / 라우터 설정
├── core/
│ ├── constants/ ← 상수 (색상, 크기, 텍스트 등)
│ ├── theme/ ← 앱 테마 정의
│ ├── network/ ← Dio 인스턴스, 인터셉터
│ ├── storage/ ← flutter_secure_storage 래퍼
│ └── utils/ ← 순수 유틸 함수
├── features/
│ └── user/
│ ├── data/
│ │ ├── models/ ← JSON 직렬화 모델
│ │ └── repositories/ ← API 호출 구현체
│ ├── domain/
│ │ └── entities/ ← 순수 비즈니스 엔티티
│ └── presentation/
│ ├── pages/ ← 화면 단위 위젯
│ ├── widgets/ ← 재사용 위젯
│ └── providers/ ← 상태 관리
└── shared/
└── widgets/ ← 앱 공통 위젯
```
---
## 위젯 작성 원칙
```dart
// ✅ 좋은 예 — 작은 위젯으로 분리
class UserProfilePage extends StatelessWidget {
const UserProfilePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: const _AppBar(),
body: const _ProfileBody(),
);
}
}
class _ProfileBody extends StatelessWidget {
const _ProfileBody();
@override
Widget build(BuildContext context) {
return Column(
children: [
const _AvatarSection(),
const _InfoSection(),
],
);
}
}
```
- 위젯 트리 depth가 깊어지면 **커스텀 위젯으로 분리**
- `build()` 메서드 내 비즈니스 로직 작성 **금지**
- `const` 생성자 적극 활용 (불필요한 리빌드 방지)
---
## 반응형 UI
```dart
// ❌ 하드코딩 금지
Container(width: 375, height: 200)
// ✅ 반응형 처리
Container(
width: MediaQuery.of(context).size.width * 0.9,
child: ...,
)
// ✅ 유연한 레이아웃
Expanded(flex: 2, child: LeftPanel()),
Expanded(flex: 1, child: RightPanel()),
```
- `Expanded`, `Flexible`, `MediaQuery`, `LayoutBuilder` 적극 활용
- 하드코딩 픽셀값 사용 **금지**
- 폰트 크기는 `Theme.of(context).textTheme` 활용
---
## 상태 관리
```dart
// Riverpod 예시 (권장)
@riverpod
Future<User> fetchUser(FetchUserRef ref, int id) async {
final repo = ref.watch(userRepositoryProvider);
return repo.getUser(id);
}
// 위젯에서 사용
class UserWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final userAsync = ref.watch(fetchUserProvider(userId));
return userAsync.when(
data: (user) => Text(user.name),
loading: () => const CircularProgressIndicator(),
error: (e, _) => const ErrorWidget(),
);
}
}
```
- **Riverpod** 권장 (Provider는 레거시로 간주)
- `StatefulWidget`은 애니메이션, 폼 등 로컬 UI 상태에만 제한적 사용
---
## API 통신 (Dio + 인터셉터)
```dart
// core/network/dio_client.dart
class DioClient {
late final Dio _dio;
DioClient() {
_dio = Dio(BaseOptions(baseUrl: Env.apiUrl));
_dio.interceptors.add(AuthInterceptor()); // 토큰 자동 첨부
_dio.interceptors.add(ErrorInterceptor()); // 에러 코드 처리
}
}
// 에러 인터셉터 — CLAUDE.md 에러 맵 기준 한국어 메시지 처리
class ErrorInterceptor extends Interceptor {
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
// 에러 코드 파싱 후 SnackBar/Dialog 표시
}
}
```
---
## 보안 (토큰 저장)
```dart
// flutter_secure_storage 사용
const storage = FlutterSecureStorage();
// 저장
await storage.write(key: 'access_token', value: token);
// 읽기
final token = await storage.read(key: 'access_token');
// 삭제 (로그아웃)
await storage.deleteAll();
```
- JWT, 민감 정보는 반드시 `flutter_secure_storage` 사용
- `SharedPreferences`에 토큰 저장 **금지**
---
## 성능 & 최신 트렌드 추가 규칙
- **모델 직렬화**: `json_serializable` + `freezed` 사용 권장 (불변 모델)
- **이미지**: `cached_network_image`로 네트워크 이미지 캐싱
- **목록**: 긴 목록은 `ListView.builder` 사용 (전체 렌더링 금지)
- **에러 핸들링**: `Either<Failure, Success>` 패턴 고려 (`fpdart` 패키지)
- **라우팅**: `go_router` 사용 권장 (딥링크, 중첩 라우팅 지원)
- **환경 분리**: `--dart-define` 또는 `flutter_dotenv`로 dev/prod 분리
+150
View File
@@ -0,0 +1,150 @@
---
description: NestJS 백엔드 개발 규칙
---
> 이 파일은 `.claude/commands/nestjs.md`에 위치한다.
> 사용법: `/nestjs [요청 내용]`
---
## 아키텍처 원칙
### 관심사 분리 (Separation of Concerns)
```
Controller → HTTP 라우팅, 요청/응답 파싱만 담당
Service → 모든 비즈니스 로직 처리
Repository → 데이터 접근 계층 (TypeORM)
DTO → 요청/응답 데이터 형식 정의 및 검증
```
- `Controller`에 비즈니스 로직 작성 **금지**
- `new` 키워드로 수동 객체 생성 **지양** → 의존성 주입(DI) 활용
### 모듈 구조
```
src/
├── modules/
│ └── user/
│ ├── user.module.ts
│ ├── user.controller.ts
│ ├── user.service.ts
│ ├── user.repository.ts ← 커스텀 Repository (선택)
│ └── dto/
│ ├── create-user.dto.ts
│ └── update-user.dto.ts
├── common/
│ ├── interceptors/ ← 응답 래핑, 에러 처리
│ ├── filters/ ← 전역 Exception Filter
│ ├── guards/ ← 인증/권한 Guard
│ ├── decorators/ ← 커스텀 데코레이터
│ └── pipes/ ← 전역 Validation Pipe
└── shared/
└── utils/ ← 순수 유틸 함수
```
---
## DTO 작성 규칙
```typescript
import { IsString, IsEmail, IsNotEmpty, MinLength } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
export class CreateUserDto {
@ApiProperty({ description: '사용자 이메일', example: 'user@example.com' })
@IsEmail({}, { message: '올바른 이메일 형식을 입력해 주세요.' })
@IsNotEmpty()
email: string;
@ApiProperty({ description: '비밀번호 (최소 8자)', example: 'password123' })
@IsString()
@MinLength(8, { message: '비밀번호는 최소 8자 이상이어야 합니다.' })
password: string;
}
```
- 모든 DTO는 `class-validator` + `class-transformer` 사용
- 에러 메시지는 **한국어**로 작성
- `@ApiProperty()` 반드시 포함 (Swagger 명세용)
---
## 표준 응답 인터셉터
```typescript
// common/interceptors/response.interceptor.ts
@Injectable()
export class ResponseInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
map((data) => ({
success: true,
data,
})),
);
}
}
```
- `main.ts`에서 전역 등록: `app.useGlobalInterceptors(new ResponseInterceptor())`
---
## 전역 Exception Filter
```typescript
// common/filters/http-exception.filter.ts
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
// 에러 코드 매핑 로직 포함
// SYS_001 / AUTH_001 / VAL_001 등 CLAUDE.md 에러 맵 참조
}
}
```
---
## Swagger 필수 적용
```typescript
// main.ts
const config = new DocumentBuilder()
.setTitle('API 문서')
.setDescription('프로젝트 API 명세서')
.setVersion('1.0')
.addBearerAuth()
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api-docs', app, document);
```
- 모든 Controller에 `@ApiTags()` 적용
- 모든 엔드포인트에 `@ApiOperation()`, `@ApiResponse()` 작성
- 모든 DTO에 `@ApiProperty()` 작성
---
## 인증 (JWT)
- Access Token: 만료 시간 짧게 (15분 ~ 1시간)
- Refresh Token: HttpOnly Secure 쿠키로 전달
- `@nestjs/jwt` + `@nestjs/passport` 사용
- Guard를 통한 라우트 보호
---
## 성능 & 최신 트렌드 추가 규칙
- **캐싱**: 자주 조회되는 데이터는 `@nestjs/cache-manager` (Redis) 활용 고려
- **Rate Limiting**: `@nestjs/throttler`로 API 남용 방지
- **Pagination**: 목록 API는 반드시 커서 기반 또는 오프셋 페이지네이션 적용
- **Logging**: `winston` 또는 `@nestjs/common Logger` 사용, `console.log` 지양
- **Health Check**: `@nestjs/terminus``/health` 엔드포인트 구성 권장
- **Versioning**: API 버전 관리 (`/v1/`, `/v2/`) 적용 고려
+161
View File
@@ -0,0 +1,161 @@
---
description: Vue 3 프론트엔드 개발 규칙
---
> 이 파일은 `.claude/commands/vue3.md`에 위치한다.
> 사용법: `/vue3 [요청 내용]`
---
## 컴포넌트 작성 원칙
### 필수 구조
```vue
<script setup lang="ts">
// 1. import
// 2. Props / Emits 정의
// 3. Store / Composable 호출
// 4. 반응형 상태 (ref, reactive, computed)
// 5. 함수 정의
// 6. Lifecycle Hooks
// 7. watch
</script>
<template>
<!-- 시맨틱 HTML 태그 사용 -->
</template>
<style scoped>
/* rem 단위 사용, 인라인 스타일 금지 */
</style>
```
- `Options API` 사용 **금지**`Composition API` + `<script setup>` 필수
- 인라인 스타일(`style=""`) 사용 **금지**`<style scoped>` 사용
- 모든 px 값은 **rem으로 변환** (기준: 16px = 1rem)
---
## Props / Emits 타입 정의
```typescript
// Props
interface Props {
title: string
count?: number
items: string[]
}
const props = withDefaults(defineProps<Props>(), {
count: 0,
})
// Emits
interface Emits {
(e: 'update', value: string): void
(e: 'close'): void
}
const emit = defineEmits<Emits>()
```
---
## 상태 관리 (Pinia)
```typescript
// stores/user.store.ts
export const useUserStore = defineStore('user', () => {
// 상태
const user = ref<User | null>(null)
const isLoggedIn = computed(() => !!user.value)
// 액션
async function fetchUser(id: number) {
// API 호출
}
return { user, isLoggedIn, fetchUser }
})
```
- 도메인별 Store 파일 분리 (`user.store.ts`, `auth.store.ts` 등)
- Store 내 API 호출은 허용, 단 에러 처리는 인터셉터에 위임
---
## API 호출 (Composables + Axios)
```typescript
// composables/useApi.ts — 중앙 Axios 인스턴스
const api = axios.create({
baseURL: import.meta.env.VITE_API_URL,
withCredentials: true, // HttpOnly 쿠키 전송
})
// 응답 인터셉터 — 에러 코드 전역 처리
api.interceptors.response.use(
(response) => response.data.data, // success: true → data 언래핑
(error) => {
const code = error.response?.data?.error?.code
// 에러 코드별 Toast 알림 처리 (CLAUDE.md 에러 맵 참조)
return Promise.reject(error)
},
)
```
```typescript
// composables/useUser.ts — 도메인별 API Composable
export function useUser() {
const loading = ref(false)
const error = ref<string | null>(null)
async function getUser(id: number) {
loading.value = true
try {
return await api.get(`/users/${id}`)
} finally {
loading.value = false
}
}
return { loading, error, getUser }
}
```
- API 호출 로직은 반드시 `composables/` 폴더에 분리
- 컴포넌트 내 `axios.get()` 직접 호출 **금지**
---
## 라우터 (Vue Router)
```typescript
// 코드 스플리팅 — 모든 페이지 컴포넌트는 lazy import
const routes = [
{
path: '/dashboard',
component: () => import('@/pages/DashboardPage.vue'),
meta: { requiresAuth: true },
},
]
// 네비게이션 가드 — 인증 처리
router.beforeEach((to) => {
const authStore = useAuthStore()
if (to.meta.requiresAuth && !authStore.isLoggedIn) {
return '/login'
}
})
```
---
## 성능 & 최신 트렌드 추가 규칙
- **컴포넌트 분리**: 200줄 이상 컴포넌트는 분리 고려
- **`defineAsyncComponent`**: 무거운 컴포넌트는 비동기 로딩 적용
- **`v-memo`**: 반복 렌더링 최적화 필요 시 활용
- **`<Suspense>`**: 비동기 컴포넌트 로딩 상태 처리에 활용
- **환경변수**: 모든 설정값은 `import.meta.env.VITE_*` 형태로 관리
- **절대경로**: `@/` 경로 alias 사용 (상대경로 `../../` 지양)
- **접근성(a11y)**: `aria-label`, `role`, `tabindex` 등 기본 접근성 속성 포함