# 아키텍처 구성
## 디렉토리 구조
```
src/
├── app/ # Next.js App Router
│ ├── (auth)/ # 인증 라우트 (login, signup)
│ ├── (protected)/ # 보호된 라우트 (메인 보드)
│ └── api/ # Route Handlers (서버 전용, DB 접근 지점)
│ ├── auth/ # signup, login, logout, me
│ ├── board/ # 보드 조회/자동 생성
│ └── cards/ # 카드 CRUD, 이동/정렬
├── components/ # React 컴포넌트
│ ├── auth/ # 인증 폼
│ ├── board/ # 칸반 보드 (KanbanBoard, Column, Card)
│ ├── modals/ # 모달 (CardFormModal, ConfirmModal)
│ └── ui/ # 기본 UI (Button, Input, Modal)
├── hooks/ # 커스텀 훅
├── lib/ # 유틸리티
│ ├── store.ts # Zustand 스토어 (fetch로 /api 호출)
│ ├── validation.ts # Zod 스키마
│ ├── constants.ts # 상수 정의
│ ├── db/ # PostgreSQL 접근 (서버 전용)
│ │ ├── pool.ts # pg Pool 싱글톤
│ │ └── queries/ # users.ts, boards.ts, cards.ts (SQL 함수)
│ └── auth/ # 인증 (서버 전용)
│ ├── password.ts # 비밀번호 해싱/검증 (bcryptjs)
│ ├── session.ts # JWT 발급/검증, 쿠키 읽기/쓰기 (jose)
│ └── middleware.ts # 미들웨어용 세션 검증/리다이렉트
└── types/ # TypeScript 타입 (DB Row 타입 포함)
```
> `lib/db/`, `lib/auth/`는 `server-only`로 보호하여 클라이언트 번들에 포함되지 않게 한다.
> 브라우저는 PostgreSQL에 직접 접속할 수 없으므로 모든 DB 접근은 Route Handler를 거친다.
## 데이터 흐름
```
UI (Components)
↓ 사용자 액션
Custom Hooks (상태 관리)
↓
Zustand Store (전역 상태 + 낙관적 업데이트)
↓ fetch('/api/...')
Route Handlers (세션 검증 → Zod 검증 → 소유권 확인)
↓
lib/db/queries (파라미터 바인딩된 SQL)
↓ node-postgres (pg) Pool
PostgreSQL 18 (로컬)
```
## 인증 흐름
자체 구현 JWT 세션 (Supabase Auth 미사용).
- 회원가입: 비밀번호를 bcrypt로 해싱하여 `users`에 저장 → 세션 쿠키 발급
- 로그인: 이메일 조회 → 해시 비교 → JWT(`sub` = user id)를 httpOnly 쿠키로 발급
- 로그아웃: 세션 쿠키 삭제
- Middleware: 모든 요청에서 JWT 서명/만료만 검증 (Edge 런타임이라 `pg` 사용 불가)
- 미인증 시 /login으로 리다이렉트
- 인증된 사용자가 /login 접근 시 /로 리다이렉트
- Route Handler / Server Component는 `getSession()`으로 사용자를 확인한 뒤 DB 조회
## 권한(소유권) 검사
RLS는 사용하지 않는다. 대신 모든 쿼리에서 소유권을 명시한다.
- boards: `WHERE user_id = $세션사용자`
- cards: `boards`와 조인하여 `boards.user_id = $세션사용자` 확인 후 조회/수정/삭제
- 카드 id만으로 접근하는 API도 반드시 소유권 조건을 포함한다 (IDOR 방지)
## 데이터베이스
PostgreSQL 18 (로컬 설치). 스키마는 `.claude/skills/postgres-setup/sql/`의 SQL 파일로 관리한다.
- **users**: 사용자 계정 (email 고유, password_hash)
- **boards**: 사용자별 보드 (user_id → users.id)
- **cards**: 보드별 카드 (board_id → boards.id, status/order로 정렬)
- PK는 PostgreSQL 18 내장 `uuidv7()` 사용 (확장 불필요, 시간순 정렬)
- `updated_at`은 트리거로 자동 갱신