Merge branch 'ycshin-node' of https://g.wace.me/jskim/vexplor_dev into jskim-node

This commit is contained in:
kjs
2026-04-01 16:38:36 +09:00
98 changed files with 8015 additions and 1100 deletions
@@ -0,0 +1,122 @@
---
name: IMX[계획] IMAP 메일 기능 확장
description: 메일 삭제, SMTP 발송, 폴더 전환, 첨부파일 다운로드, 이동, 답장/전달 구현
type: plan
---
# IMX 계획 — IMAP 메일 기능 확장
## 개요
기존 메일 조회/읽음처리만 되던 IMAP 페이지에 전체 메일 클라이언트 기능 추가.
nodemailer(이미 설치), imapflow(이미 설치), TipTap v2(신규 설치) 기반.
## 현재 동작
- 계정 목록 조회
- 메일 스트리밍 목록
- 메일 상세 보기
- 읽음 처리
- 메일 삭제 (백엔드만, UI 버튼 있으나 실제 연동 확인 필요)
## 변경 후 동작
- 좌측 패널: 폴더 목록 (INBOX, Sent, Trash, Spam 등) + 미읽음 수
- 메일 상세 우측 버튼: 답장 / 전달 / 이동 / 삭제(→Trash)
- 하단 첨부파일 목록 + 다운로드 버튼
- 우상단 `작성` 버튼 → Dialog (TipTap 에디터, to/cc/subject)
- 답장/전달 시 원문 인용 자동 삽입
## 시각적 예시
```
┌──────────────────────────────────────────────────────────────────┐
│ 메일 관리 (IMAP) [작성] [계정추가] │
├──────────────┬───────────────────┬───────────────────────────────┤
│ [계정목록] │ [검색창] │ 제목: 보안 알림 │
│ │ │ From: Google │
│ Gmail │ ● Google 오후1:51 │ To: yechul@gmail.com │
│ Wace │ 보안 알림 │ Date: 2026-03-27 │
│ │ ● GitHub 오전9:48 │ [답장][전달][이동▼][삭제] │
│ ───────── │ Sudo code │ ───────────────────────────── │
│ [폴더목록] │ │ <HTML 본문> │
│ INBOX (3) │ │ │
│ Sent │ │ 📎 첨부파일 │
│ Trash │ │ file.pdf (120KB) [다운로드] │
│ Spam │ │ │
└──────────────┴───────────────────┴───────────────────────────────┘
```
## 아키텍처
```mermaid
graph TD
FE[page.tsx] -->|GET /folders| BE_CTRL[userMailController]
FE -->|POST /send| BE_CTRL
FE -->|POST /move| BE_CTRL
FE -->|GET /attachment| BE_CTRL
BE_CTRL --> IMAP[userMailImapService]
BE_CTRL --> SMTP[userMailSmtpService - 신규]
IMAP --> Pool[imapConnectionPool]
SMTP --> Nodemailer[nodemailer]
```
## 변경 파일
### 신규
- `backend-node/src/services/userMailSmtpService.ts` — SMTP 발송 전용
### 수정
- `backend-node/src/services/userMailImapService.ts` — 폴더목록, 이동, 첨부파일 추가
- `backend-node/src/controllers/userMailController.ts` — 신규 엔드포인트 핸들러
- `backend-node/src/routes/userMailRoutes.ts` — 신규 라우트 등록
- `frontend/lib/api/userMail.ts` — 신규 API 함수
- `frontend/app/(main)/mail/imap/page.tsx` — UI 전면 확장
## 신규 API 엔드포인트
| Method | Path | 설명 |
|--------|------|------|
| GET | `/user-mail/accounts/:id/folders` | 폴더 목록 + 미읽음 수 |
| GET | `/user-mail/accounts/:id/folders/:folder/mails/stream` | 폴더별 메일 스트리밍 |
| POST | `/user-mail/accounts/:id/mails/:seqno/move` | 메일 이동 `{ targetFolder }` |
| GET | `/user-mail/accounts/:id/mails/:seqno/attachment/:partId` | 첨부파일 다운로드 (스트리밍) |
| POST | `/user-mail/accounts/:id/send` | 메일 발송 `{ to, cc, subject, html, text, inReplyTo?, references? }` |
## 코드 설계
### userMailSmtpService.ts
```typescript
// SMTP 포트 추론: useTls true → 465, false → 587
// Gmail: smtp.gmail.com, wace.me: mail.wace.me 또는 host 에서 도메인 추출
// nodemailer createTransport + sendMail
// 답장: inReplyTo, references 헤더 설정
```
### userMailImapService.ts 추가 메서드
```typescript
listFolders(account): Promise<{ path, name, unseen }[]> // client.list({ statusQuery })
moveMail(account, seqno, targetFolder): Promise<Result> // messageMove
downloadAttachment(account, seqno, partId, res): Promise<void> // download() + pipeline(res)
```
### page.tsx 추가 UI
- `folders` state: 폴더 목록
- `currentFolder` state: 현재 폴더 (기본 INBOX)
- `ComposeDialog`: TipTap 에디터 + to/cc/subject 필드
- `composeMode`: 'new' | 'reply' | 'forward'
- 메일 상세 버튼: 답장, 전달, 이동(DropdownMenu), 삭제
## 예상 문제
1. **Gmail SMTP 포트**: Gmail은 587(STARTTLS) 또는 465(SSL). host에서 자동 추론.
2. **폴더명 인코딩**: 한글 폴더 등 UTF-7/UTF-8 혼용 → imapflow가 자동 처리
3. **첨부파일 partId**: bodyStructure 파싱이 복잡 → `client.download(seqno, partId)` 직접 사용
4. **TipTap SSR**: Next.js에서 dynamic import 필요 (`ssr: false`)
## 설계 원칙
- SMTP 서비스는 IMAP 서비스와 완전 분리 (파일 분리)
- 첨부파일은 서버에 저장하지 않고 스트리밍으로 직접 응답
- 답장/전달 인용: `<blockquote>` + RFC 2822 헤더 표준 준수
- TipTap은 dynamic import로 SSR 방지
@@ -0,0 +1,58 @@
---
name: IMX[맥락] IMAP 메일 기능 확장
description: 왜 이 기능들을 추가하는가, 핵심 결정 근거
type: context
---
# IMX 맥락 — IMAP 메일 기능 확장
## 왜 하는가
ERP 시스템에서 메일 확인만 되면 의미가 없음. 거래처 메일 수신 후 바로 답장, 견적서 첨부파일 저장, 담당자 전달까지 워크플로우가 연결되어야 실용적.
## 핵심 결정 + 근거
### SMTP 서비스 분리 (`userMailSmtpService.ts`)
- IMAP(수신)과 SMTP(송신)은 프로토콜 자체가 다름
- 파일 분리로 각각 독립적으로 교체/테스트 가능
- nodemailer는 이미 설치됨 (추가 의존성 없음)
### TipTap v2 선택 (메일 에디터)
- ProseMirror 기반 → 안정적, 확장 용이
- `@tiptap/react` 공식 패키지 → Next.js 15 호환
- Quill보다 번들 크기 작음 (~100KB vs ~200KB)
- dynamic import (`ssr: false`)로 SSR 문제 회피
### 첨부파일 스트리밍
- 서버에 임시 저장하지 않음 → 디스크 절약, 보안
- `imapflow client.download()``stream/promises pipeline()` → HTTP 응답
- 대용량 파일도 메모리 부담 없음
### 메일 삭제 = Trash 이동
- 즉시 삭제(`messageDelete`) 대신 Trash 폴더 이동
- 실수로 삭제 시 복구 가능
- Gmail/wace.me 모두 Trash 폴더 표준 지원
### 폴더 구조 표시
- `client.list({ statusQuery: { unseen: true } })` 로 미읽음 수 포함
- 폴더 클릭 시 기존 스트리밍 로직 재활용 (folder 파라미터 추가)
### 답장/전달 RFC 준수
- `inReplyTo`, `references` 헤더 → 메일 클라이언트에서 스레드로 묶임
- `<blockquote>` 인용 → Gmail/Outlook 모두 올바르게 렌더링
## 관련 파일
- `backend-node/src/services/userMailImapService.ts` — IMAP 수신 로직
- `backend-node/src/services/imapConnectionPool.ts` — 커넥션 풀 (건드리지 않음)
- `backend-node/src/services/mailCache.ts` — TTL 캐시 (건드리지 않음)
- `frontend/app/(main)/mail/imap/page.tsx` — 메인 UI
## 기술 참고
- imapflow `client.list()` → statusQuery 옵션으로 unseen 포함
- imapflow `client.messageMove(seqno, folder)` → UID 기반 이동
- imapflow `client.download(seqno, partId)` → ReadableStream 반환
- nodemailer `createTransport({ host, port, secure, auth })``sendMail()`
- RFC 2822 §3.6.4: `In-Reply-To`, `References` 헤더
- W3C HTML Threading: `<blockquote cite="mid:...">` 권장
@@ -0,0 +1,58 @@
---
name: IMX[체크] IMAP 메일 기능 확장
description: 구현 및 검증 체크리스트
type: checklist
---
# IMX 체크리스트 — IMAP 메일 기능 확장
## 공정 상태: 0%
## 구현 체크리스트
### Unit A — 백엔드 서비스
- [ ] `userMailImapService.ts`: `listFolders()` 추가
- [ ] `userMailImapService.ts`: `streamMailsByFolder()` 추가
- [ ] `userMailImapService.ts`: `moveMail()` 추가
- [ ] `userMailImapService.ts`: `downloadAttachment()` 추가
- [ ] `userMailSmtpService.ts` 신규 생성 (nodemailer 기반)
- [ ] TypeScript 에러 없음
### Unit B — 백엔드 컨트롤러/라우트
- [ ] `userMailController.ts`: `listFolders`, `streamFolderMails`, `moveMail`, `downloadAttachment`, `sendMail` 핸들러
- [ ] `userMailRoutes.ts`: 5개 신규 라우트 등록
- [ ] TypeScript 에러 없음
### Unit C — 프론트엔드 API
- [ ] `userMail.ts`: `getUserMailFolders()` 추가
- [ ] `userMail.ts`: `streamFolderMails()` 추가
- [ ] `userMail.ts`: `moveUserMail()` 추가
- [ ] `userMail.ts`: `sendUserMail()` 추가
- [ ] `userMail.ts`: 첨부파일 다운로드 URL 헬퍼 추가
### Unit D — 프론트엔드 UI (TipTap 설치 포함)
- [ ] TipTap 패키지 설치 (`@tiptap/react`, `@tiptap/starter-kit`, `@tiptap/extension-link`)
- [ ] 좌측 패널: 폴더 목록 + 미읽음 수
- [ ] 폴더 클릭 → 해당 폴더 메일 스트리밍
- [ ] 메일 상세: 답장/전달/이동/삭제 버튼
- [ ] ComposeDialog: TipTap 에디터 + to/cc/subject
- [ ] 답장/전달 시 원문 인용 자동 삽입
- [ ] 첨부파일 목록 + 다운로드 링크
- [ ] TypeScript 에러 없음
## 검증 체크리스트
- [ ] 폴더 목록이 좌측 패널에 표시됨 (INBOX, Sent, Trash 등)
- [ ] 폴더 클릭 시 해당 폴더 메일이 로드됨
- [ ] 메일 삭제 버튼 클릭 → Trash로 이동됨
- [ ] 메일 이동 드롭다운 → 다른 폴더로 이동됨
- [ ] 첨부파일 있는 메일에서 다운로드 버튼 동작
- [ ] 새 메일 작성 → 발송 성공
- [ ] 답장 → To 자동 입력, 원문 인용 포함
- [ ] 전달 → 원문 전체 포함
## 변경 이력
| 일자 | 내용 |
|------|------|
| 2026-03-27 | PCC 작성, 구현 시작 |
+212
View File
@@ -0,0 +1,212 @@
# MSN[계획] 메신저 기능 개발
## 개요
벡스플로어 ERP에 내장 메신저 기능을 추가한다. Gmail 편지쓰기 스타일의 우측 하단 플로팅 모달로 동작하며, Socket.IO 기반 실시간 통신을 제공한다. 모든 화면에서 접근 가능하고, 동일 company_code 내 사용자끼리 1:1 DM / 그룹 채팅 / 채널 대화를 지원한다.
---
## 현재 동작
- 메신저 기능 없음
- 사내 커뮤니케이션 수단 부재
## 변경 후 동작
- 모든 화면 우측 하단에 메신저 FAB 버튼 고정 (z-index: 9999)
- FAB 클릭 시 Gmail 편지쓰기 스타일 모달 팝업 (우측 하단)
- 모달 좌측: 채팅방 목록 (DM / 그룹 / 채널 탭)
- 모달 우측: 채팅 영역 (메시지 입력, 파일 첨부, 이모지, 멘션, 스레드)
- Socket.IO로 실시간 메시지 수신
- 읽지 않은 메시지 수 FAB 배지 표시
- 토스트 알림 on/off 토글 (메신저 설정 내)
---
## 시각적 예시
```
┌─────────────────────────────────────────────────────┐
│ 벡스플로어 화면 │
│ │
│ │
│ ┌────────────────────┐ │
│ │ 채팅방 목록 │ 채팅창 │ │
│ │─────────────────── │ │
│ │ DM 그룹 채널 │ │
│ │ ───────────────── │ │
│ │ 👤 김민호 ● │ │
│ │ 👥 개발팀 │ │
│ │ # 공지사항 │ │
│ └────────────────────┘ │
│ [💬 3] │
└─────────────────────────────────────────────────────┘
```
---
## 아키텍처
```mermaid
graph TB
subgraph Frontend
FAB[MessengerFAB] --> Modal[MessengerModal]
Modal --> RoomList[RoomList 좌측 240px]
Modal --> ChatPanel[ChatPanel 우측 480px]
ChatPanel --> MessageList[MessageList]
ChatPanel --> MessageInput[MessageInput]
MessageInput --> FileUpload[파일 첨부]
MessageInput --> EmojiPicker[이모지]
MessageInput --> MentionDropdown[멘션 @]
end
subgraph SocketIO
SocketClient[socket.io-client] <--> SocketServer[messengerSocket.ts]
end
subgraph Backend
SocketServer --> MessengerService[messengerService.ts]
Route[messengerRoutes.ts] --> Controller[messengerController.ts]
Controller --> MessengerService
MessengerService --> DB[(PostgreSQL)]
FileRoute[파일 업로드] --> Multer[multerMessengerConfig.ts]
end
Frontend <--> SocketIO
Frontend <--> Route
```
---
## 변경 파일
### 신규 생성
| 파일 | 역할 |
|------|------|
| `backend-node/src/types/messenger.ts` | TypeScript 인터페이스 |
| `backend-node/src/services/messengerService.ts` | 비즈니스 로직 |
| `backend-node/src/controllers/messengerController.ts` | HTTP 핸들러 |
| `backend-node/src/routes/messengerRoutes.ts` | REST API 라우트 |
| `backend-node/src/socket/messengerSocket.ts` | Socket.IO 이벤트 핸들러 |
| `backend-node/src/config/multerMessengerConfig.ts` | 파일 업로드 설정 |
| `db/migrations/messenger_tables.sql` | DB 테이블 생성 |
| `frontend/components/messenger/MessengerFAB.tsx` | 플로팅 버튼 |
| `frontend/components/messenger/MessengerModal.tsx` | 메인 모달 컨테이너 |
| `frontend/components/messenger/RoomList.tsx` | 채팅방 목록 |
| `frontend/components/messenger/ChatPanel.tsx` | 채팅 영역 |
| `frontend/components/messenger/MessageItem.tsx` | 메시지 단일 아이템 |
| `frontend/components/messenger/MessageInput.tsx` | 입력창 |
| `frontend/components/messenger/NewRoomModal.tsx` | 방 생성 모달 |
| `frontend/components/messenger/UserAvatar.tsx` | 프로필 아바타 |
| `frontend/hooks/useMessenger.ts` | 메신저 상태 훅 |
| `frontend/hooks/useMessengerSocket.ts` | Socket.IO 훅 |
| `frontend/contexts/MessengerContext.tsx` | 전역 메신저 상태 |
### 수정
| 파일 | 변경 내용 |
|------|-----------|
| `backend-node/src/app.ts` | Socket.IO 서버 초기화, messengerRoutes 등록 |
| `frontend/app/(main)/layout.tsx` | `<MessengerProvider>`, `<MessengerFAB />` 추가 |
---
## 코드 설계
### DB 스키마
```sql
-- 채팅방
messenger_rooms (
room_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
company_code VARCHAR(50) NOT NULL,
room_type VARCHAR(10) NOT NULL CHECK (room_type IN ('dm', 'group', 'channel')),
room_name VARCHAR(100),
created_by VARCHAR(100) NOT NULL,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
)
-- 참여자
messenger_participants (
room_id UUID REFERENCES messenger_rooms(room_id),
user_id VARCHAR(100) NOT NULL,
company_code VARCHAR(50) NOT NULL,
joined_at TIMESTAMP DEFAULT NOW(),
last_read_at TIMESTAMP DEFAULT NOW(),
PRIMARY KEY (room_id, user_id)
)
-- 메시지
messenger_messages (
message_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
room_id UUID REFERENCES messenger_rooms(room_id),
company_code VARCHAR(50) NOT NULL,
sender_id VARCHAR(100) NOT NULL,
content TEXT,
message_type VARCHAR(10) DEFAULT 'text' CHECK (message_type IN ('text', 'file', 'system')),
parent_message_id UUID REFERENCES messenger_messages(message_id),
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
is_deleted BOOLEAN DEFAULT FALSE
)
-- 이모지 리액션
messenger_reactions (
message_id UUID REFERENCES messenger_messages(message_id),
user_id VARCHAR(100) NOT NULL,
emoji VARCHAR(10) NOT NULL,
created_at TIMESTAMP DEFAULT NOW(),
PRIMARY KEY (message_id, user_id, emoji)
)
-- 파일 첨부
messenger_files (
file_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
message_id UUID REFERENCES messenger_messages(message_id),
filename VARCHAR(255) NOT NULL,
original_name VARCHAR(255) NOT NULL,
file_size BIGINT NOT NULL,
mime_type VARCHAR(100),
created_at TIMESTAMP DEFAULT NOW()
)
```
### Socket.IO 이벤트
| 이벤트 | 방향 | 설명 |
|--------|------|------|
| `join_rooms` | Client→Server | 참여 중인 방 전체 구독 |
| `send_message` | Client→Server | 메시지 전송 |
| `new_message` | Server→Client | 새 메시지 수신 |
| `message_read` | Client→Server | 읽음 처리 |
| `user_online` | Server→Client | 온라인 상태 변경 |
| `typing_start/stop` | Client↔Server | 타이핑 표시 |
| `add_reaction` | Client→Server | 이모지 리액션 추가 |
| `reaction_updated` | Server→Client | 리액션 업데이트 |
### REST API
| Method | URL | 설명 |
|--------|-----|------|
| GET | `/api/messenger/rooms` | 내 채팅방 목록 |
| POST | `/api/messenger/rooms` | 채팅방 생성 |
| GET | `/api/messenger/rooms/:roomId/messages` | 메시지 히스토리 |
| POST | `/api/messenger/rooms/:roomId/read` | 읽음 처리 |
| POST | `/api/messenger/files/upload` | 파일 업로드 |
| GET | `/api/messenger/files/:fileId` | 파일 다운로드 |
| GET | `/api/messenger/users` | 회사 내 사용자 목록 |
| PUT | `/api/messenger/rooms/:roomId` | 방 이름/설정 수정 |
---
## 예상 문제
1. **Socket.IO + Next.js**: Next.js는 기본적으로 HTTP 서버를 추상화하므로, Express HTTP 서버에 Socket.IO를 붙이고 프론트엔드는 백엔드 포트로 직접 연결
2. **JWT 인증 with Socket.IO**: handshake 시 Authorization 헤더 또는 auth 옵션으로 토큰 전달, 서버에서 미들웨어로 검증
3. **채널 분리 가능성**: `room_type = 'channel'` 쿼리 조건으로만 필터링하므로, 채널 UI/라우트만 제거하면 기능 완전 제거 가능
---
## 설계 원칙
- 기존 MVC + Service 패턴 준수
- 모든 쿼리에 `company_code` 필터 적용 (멀티테넌시)
- 채널은 `room_type` 값으로만 분리 — 코드 결합도 최소화
- FAB/모달은 기존 레이아웃 DOM에 독립적으로 렌더링 (Portal 또는 최상단 마운트)
+61
View File
@@ -0,0 +1,61 @@
# MSN[맥락] 메신저 기능 개발
## 왜 하는가
벡스플로어 ERP 내에서 사용자 간 실시간 커뮤니케이션 수단이 없다. 업무 맥락을 ERP 밖(카카오톡, 슬랙 등)으로 내보내지 않고 시스템 안에서 처리할 수 있도록 내장 메신저를 도입한다.
---
## 핵심 결정 및 근거
| 결정 | 근거 |
|------|------|
| Socket.IO 신규 도입 | HTTP 폴링은 메신저에 부적합. 실시간 타이핑 표시, 온라인 상태, 즉각적인 메시지 수신이 필요 |
| Gmail 스타일 우측 하단 모달 | 업무 화면을 방해하지 않고 언제든 접근 가능. 별도 페이지 이동 없이 사용 |
| DM + 그룹 + 채널 1차 구현 | 채널은 `room_type` 값으로만 분리하여 나중에 제거 용이하게 설계 |
| company_code 격리 | 기존 멀티테넌시 패턴과 동일하게 적용. 회사 간 데이터 유출 방지 |
| 파일 업로드: multer 로컬 | 기존 메일 첨부파일과 동일한 방식. 인프라 추가 없이 일관성 유지 |
| 프로필: photo BLOB 활용 | `user_info.photo` 컬럼에 이미 이미지 저장됨. 없으면 이름 첫 글자 원형 아바타 |
| 토스트 알림 on/off | 집중 업무 시 알림 방해 방지. localStorage에 설정 저장 |
---
## 관련 파일
### 참고 패턴 (기존 코드)
| 파일 | 참고 목적 |
|------|-----------|
| `backend-node/src/config/multerConfig.ts` | 파일 업로드 설정 패턴 |
| `backend-node/src/services/authService.ts` | user_info.photo BLOB → base64 변환 패턴 |
| `backend-node/src/middleware/authMiddleware.ts` | JWT 인증 미들웨어 (Socket.IO handshake에도 동일 로직 적용) |
| `frontend/components/mail/` | UI 컴포넌트 구조 참고 |
| `frontend/hooks/use-toast.ts` | 기존 토스트 훅 사용 |
| `backend-node/src/app.ts` | 라우트 등록 및 미들웨어 설정 위치 |
---
## 기술 참고
### Socket.IO JWT 인증
```typescript
// 서버: handshake 시 토큰 검증
io.use((socket, next) => {
const token = socket.handshake.auth.token;
const user = verifyJWT(token);
socket.data.user = user;
next();
});
// 클라이언트: 연결 시 토큰 전달
const socket = io(BACKEND_URL, {
auth: { token: localStorage.getItem('token') }
});
```
### 채널 분리 설계
채널 기능은 `room_type = 'channel'` 조건으로만 분리되어 있음.
제거 시: RoomList의 채널 탭 UI 제거 + `/api/messenger/rooms?type=channel` 호출 제거만으로 완전 비활성화 가능. DB 스키마 변경 불필요.
### 멀티테넌시 적용
모든 쿼리에 `WHERE company_code = $n` 조건 필수 적용.
Socket.IO 룸 네이밍: `{company_code}:{room_id}` 형식으로 회사별 격리.
+97
View File
@@ -0,0 +1,97 @@
# MSN[체크] 메신저 기능 개발
## 공정 상태: 90% (1차 구현 완료, 2차 테스트 대기)
---
## 구현 체크리스트
### Phase 1: DB & 백엔드 기반
- [x] `db/migrations/messenger_tables.sql` 작성 및 실행
- [x] `backend-node/src/types/messenger.ts` 타입 정의
- [x] `backend-node/src/services/messengerService.ts` 구현
- [x] getRooms (내 채팅방 목록)
- [x] createRoom (DM / 그룹 / 채널)
- [x] getMessages (메시지 히스토리, 페이지네이션)
- [x] sendMessage
- [x] markAsRead
- [x] getCompanyUsers (사용자 목록)
- [x] addReaction / removeReaction
- [x] `backend-node/src/controllers/messengerController.ts` 구현
- [x] `backend-node/src/routes/messengerRoutes.ts` 구현
- [x] `backend-node/src/config/multerMessengerConfig.ts` 구현 (파일 업로드)
- [x] `backend-node/src/socket/messengerSocket.ts` 구현
- [x] JWT 인증 미들웨어
- [x] join_rooms 이벤트
- [x] send_message 이벤트
- [x] message_read 이벤트
- [x] typing_start / typing_stop 이벤트
- [x] add_reaction 이벤트
- [x] 온라인 상태 관리 (connect / disconnect)
- [x] `backend-node/src/app.ts` Socket.IO 초기화 및 라우트 등록
### Phase 2: 프론트엔드 컴포넌트
- [x] `frontend/contexts/MessengerContext.tsx` (전역 상태)
- [x] `frontend/hooks/useMessengerSocket.ts` (Socket.IO 연결 관리)
- [x] `frontend/hooks/useMessenger.ts` (채팅방/메시지 React Query)
- [x] `frontend/components/messenger/UserAvatar.tsx` (프로필 이미지 / 이름 첫 글자)
- [x] `frontend/components/messenger/MessengerFAB.tsx` (플로팅 버튼 + 배지)
- [x] `frontend/components/messenger/MessengerModal.tsx` (메인 모달 컨테이너)
- [x] `frontend/components/messenger/RoomList.tsx` (채팅방 목록 + DM/그룹/채널 탭)
- [x] `frontend/components/messenger/ChatPanel.tsx` (채팅 영역)
- [x] `frontend/components/messenger/MessageItem.tsx` (메시지 + 리액션 + 스레드 버튼)
- [x] `frontend/components/messenger/MessageInput.tsx` (입력창 + 파일 + 이모지 + 멘션)
- [x] `frontend/components/messenger/NewRoomModal.tsx` (방 생성 모달)
- [x] `frontend/components/messenger/MessengerSettings.tsx` (토스트 알림 on/off 등)
- [x] `frontend/app/(main)/layout.tsx` MessengerProvider + MessengerFAB 추가
---
## 검증 체크리스트
### 기본 동작
- [ ] FAB 버튼이 모든 페이지에서 우측 하단 고정 (z-index 최상위)
- [ ] FAB 클릭 시 모달 열기/닫기 동작
- [ ] 모달 크기: 좌측 240px + 우측 480px
### 채팅
- [ ] DM 방 생성 (사용자 선택 → 방 생성)
- [ ] 그룹 방 생성 (여러 사용자 선택 → 방 이름 입력 → 생성)
- [ ] 채널 방 생성
- [ ] 메시지 전송 및 실시간 수신 (Socket.IO)
- [ ] 메시지 히스토리 로드 (스크롤 시 이전 메시지)
### 부가 기능
- [ ] 파일 첨부 업로드/다운로드
- [ ] 이모지 리액션 추가/제거
- [ ] 멘션(@) 자동완성 드롭다운
- [ ] 스레드 답글
- [ ] 타이핑 표시 ("김민호님이 입력 중...")
### 알림
- [ ] 읽지 않은 메시지 수 FAB 배지 표시
- [ ] 다른 방 메시지 수신 시 토스트 알림
- [ ] 토스트 알림 on/off 토글 동작
- [ ] 토스트 설정값 localStorage 저장/복원
### 프로필
- [ ] photo 있는 사용자: 원형 이미지 표시
- [ ] photo 없는 사용자: 이름 첫 글자 원형 아바타 표시
### 멀티테넌시
- [ ] 다른 company_code 사용자 목록에 미노출
- [ ] 다른 회사 채팅방 접근 불가
---
## 정리
- [ ] DB 체크포인트 파일 삭제 (2차 테스트 완료 후)
---
## 변경 이력
| 날짜 | 내용 |
|------|------|
| 2026-03-30 | 최초 설계 확정 |
+169
View File
@@ -0,0 +1,169 @@
# UML[계획] - 사용자 메일 관리 시스템
## 개요
벡스플로우(Vexflow) 사용자 메일 관리 페이지 구현 프로젝트입니다. 외부 메일 서버(POP3/IMAP)와 연동하여 사용자가 본인의 메일 계정을 등록하고 벡스플로우 내에서 메일을 조회할 수 있는 기능을 제공합니다.
### 현재 동작
- Admin 메일 시스템만 존재
- JSON 파일 기반 저장소
- 사용자 구분 없음
### 변경 후 동작
- 사용자가 외부 메일 계정(IMAP 또는 POP3) 등록
- 벡스플로우에서 해당 계정의 메일 조회
- PostgreSQL 기반 계정 저장 및 관리
- 사용자별 격리(user_id 기반)
---
## 아키텍처
```
┌─────────────┐
│ 사용자 │
│ (Frontend) │
└──────┬──────┘
├─→ /mail/imap 페이지
└─→ /mail/pop3 페이지
┌──────────────────┐
│ userMail.ts │ (API 클라이언트)
│ (lib/api/) │
└────────┬─────────┘
┌────────────────────────────┐
│ /api/user-mail/* 라우트 │
│ (userMailController) │
└────────┬───────────────────┘
┌───────┴────────┐
│ │
↓ ↓
┌────────────────┐ ┌──────────────┐
│ userMailAccount│ │ userMailImap │
│ Service │ │ Service │
│ (PostgreSQL) │ │ (IMAP) │
└────────────────┘ │ │
└──────────────┘
┌──────────────────┐
│ 외부 IMAP 서버 │
└──────────────────┘
또는
┌──────────────────┐
│ userMailPop3 │
│ Service │
│ (POP3) │
└──────────────────┘
┌──────────────────┐
│ 외부 POP3 서버 │
└──────────────────┘
```
---
## 신규 파일 목록
### 백엔드 (Node.js/Express)
| 파일 경로 | 역할 |
|----------|------|
| `src/services/userMailAccountService.ts` | DB 계정 관리 (생성, 조회, 삭제, 수정) |
| `src/services/userMailImapService.ts` | IMAP 프로토콜 연결 및 메일 조회 |
| `src/services/userMailPop3Service.ts` | POP3 프로토콜 연결 및 메일 조회 |
| `src/controllers/userMailController.ts` | API 엔드포인트 처리 |
| `src/routes/userMailRoutes.ts` | 라우트 정의 |
### 프론트엔드 (React/TypeScript)
| 파일 경로 | 역할 |
|----------|------|
| `frontend/lib/api/userMail.ts` | API 클라이언트 |
| `frontend/app/(main)/mail/imap/page.tsx` | IMAP 메일 관리 페이지 |
| `frontend/app/(main)/mail/pop3/page.tsx` | POP3 메일 관리 페이지 |
---
## 수정 파일 목록
| 파일 경로 | 변경 사항 |
|----------|---------|
| `src/runMigration.ts` | 마이그레이션 스크립트에 user_mail_accounts 테이블 추가 |
| `src/app.ts` | userMailRoutes 등록 |
| `src/components/AdminPageRenderer.tsx` | /mail/imap, /mail/pop3 페이지 하드코딩 등록 (2줄) |
---
## 데이터베이스 스키마
### user_mail_accounts 테이블
```sql
CREATE TABLE user_mail_accounts (
id SERIAL PRIMARY KEY,
user_id INT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
protocol VARCHAR(10) NOT NULL CHECK (protocol IN ('imap', 'pop3')),
host VARCHAR(255) NOT NULL,
port INT NOT NULL DEFAULT 993,
use_tls BOOLEAN DEFAULT TRUE,
username VARCHAR(255) NOT NULL,
password TEXT NOT NULL, -- 암호화됨 (encryptionService 사용)
status VARCHAR(20) DEFAULT 'active' CHECK (status IN ('active', 'inactive')),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(user_id, protocol, host, username)
);
```
---
## 설계 원칙
### 1. 사용자 격리
- 모든 API 요청에서 현재 사용자의 user_id 검증
- 다른 사용자의 계정/메일 접근 불가
### 2. 프로토콜별 서비스 분리
- userMailImapService.ts: IMAP 전용
- userMailPop3Service.ts: POP3 전용
- 각 서비스는 독립적으로 동작
### 3. 기존 기능 재활용
- `encryptionService`: 비밀번호 암호화/복호화
- `mailparser`: 메일 본문 파싱
- `imap` 패키지: IMAP 연결(기존 mailReceiveBasicService 참조)
### 4. 기존 Admin 메일 시스템과 분리
- 새로운 테이블, 서비스, 라우트로 완전 독립
- JSON 파일 기반 방식 미사용
---
## 주요 API 엔드포인트
| 메서드 | 경로 | 설명 |
|--------|------|------|
| POST | `/api/user-mail/accounts` | 새 계정 등록 |
| GET | `/api/user-mail/accounts` | 사용자 계정 목록 |
| GET | `/api/user-mail/accounts/:id` | 계정 상세 조회 |
| PUT | `/api/user-mail/accounts/:id` | 계정 수정 |
| DELETE | `/api/user-mail/accounts/:id` | 계정 삭제 |
| POST | `/api/user-mail/accounts/:id/test` | 연결 테스트 |
| GET | `/api/user-mail/accounts/:id/mails` | 메일 목록 조회 |
---
## 변경 이력
| 날짜 | 버전 | 내용 |
|------|------|------|
| 2026-03-27 | v1.0 | 초안 작성 |
+147
View File
@@ -0,0 +1,147 @@
# UML[맥락] - 사용자 메일 관리 시스템
## 프로젝트 배경
### 추진 이유
- 팀장 지시로 POP3 구현 필요
- IMAP 허용 여부 확인 대기 중
- 두 프로토콜 모두 구현 후 비교하여 최적 솔루션 채택
---
## 핵심 기술 결정 사항
### 1. 페이지 등록 방식: 하드코딩
**선택**: 하드코딩 (AdminPageRenderer.tsx에 직접 등록)
**사유**:
- 컴포넌트 레지스트리에 추가할 권한 없음
- 간단한 추가 작업으로 빠른 구현 가능
**구현**:
```typescript
// AdminPageRenderer.tsx에 2줄 추가
{path: '/mail/imap', label: '메일(IMAP)', component: () => <IMapPage /> },
{path: '/mail/pop3', label: '메일(POP3)', component: () => <Pop3Page /> },
```
---
### 2. 저장소: PostgreSQL (Admin 메일과 완전 분리)
**선택**: PostgreSQL `user_mail_accounts` 테이블
**사유**:
- Admin 메일 시스템(JSON 파일 기반)과 완전 독립
- 사용자별 격리 용이 (user_id 기반)
- 확장성 및 성능 이점
**결과**:
- 기존 Admin 메일: JSON 파일 유지
- 신규 사용자 메일: PostgreSQL 관리
---
### 3. POP3 메일 삭제 정책: 서버 유지
**선택**: DELE 명령 미호출 (서버 메일 유지)
**사유**:
- 데이터 손실 방지
- 사용자 실수로 인한 피해 최소화
- 벡스플로우는 조회만 수행
**구현**:
- `userMailPop3Service.ts`에서 RETR 후 DELE 호출 안 함
- 서버의 자동 정리 정책에 의존
---
### 4. 페이지별 프로토콜 고정
**선택**: 페이지당 프로토콜 1개로 제한
**구현**:
- `/mail/imap` → IMAP 계정만 표시/관리
- `/mail/pop3` → POP3 계정만 표시/관리
**사유**:
- UI 단순화
- 프로토콜별 메일 구조 차이 처리 용이
- 사용자 혼동 최소화
---
## 관련 기존 코드 참조
### mailReceiveBasicService.ts
- IMAP 연결 및 메일 조회 로직
- 메일 파싱 및 저장 방식
- Error handling 패턴
**참조 사항**:
```typescript
// IMAP 연결 구조, 메일 검색 쿼리, 메일 수신 처리 방식
```
### encryptionService.ts
- 비밀번호 암호화/복호화
- DB 저장 시 암호화, 조회 시 복호화
**사용 방식**:
```typescript
// 저장: encryptionService.encrypt(password)
// 조회: encryptionService.decrypt(encrypted_password)
```
### AdminPageRenderer.tsx
- 기존 페이지 하드코딩 구조
- 페이지 등록 형식 및 라벨 지정 방식
**추가 위치**:
```typescript
// 기존 페이지 목록에 /mail/imap, /mail/pop3 추가
```
---
## 기술 스택 및 패키지
### 기존 패키지 (재활용)
| 패키지 | 버전 | 용도 |
|--------|------|------|
| `imap` | - | IMAP 연결 |
| `mailparser` | - | 메일 파싱 |
| `pg` | - | PostgreSQL 클라이언트 |
### 신규 패키지
| 패키지 | 버전 | 용도 |
|--------|------|------|
| `node-pop3` | latest | POP3 연결 |
---
## 핵심 고려 사항
### 보안
1. 메일 계정 비밀번호는 항상 암호화 상태로 저장
2. 사용자 격리: user_id 기반 접근 제어
3. 외부 서버 연결 정보는 민감: 환경변수 활용
### 성능
1. 메일 조회는 페이지네이션 처리
2. 연결 테스트는 별도 API (현재 메일 검색과 분리)
3. 대량 메일 처리 시 비동기 처리
### 에러 처리
1. 네트워크 오류: 재시도 로직
2. 인증 실패: 명확한 에러 메시지 제공
3. DB 오류: 트랜잭션 롤백
---
## 변경 이력
| 날짜 | 내용 |
|------|------|
| 2026-03-27 | 초안 작성 |
+161
View File
@@ -0,0 +1,161 @@
# UML[체크] - 사용자 메일 관리 시스템
## 공정 상태
**진행률: 90%** (IMAP 완성, POP3 미구현)
---
## 구현 체크리스트
### 데이터베이스
- [x] DB 마이그레이션 작성 (user_mail_accounts 테이블 생성)
### 패키지 설치
- [ ] npm install node-pop3 (설치됨, 서비스 미구현)
### 백엔드 서비스 계층
- [x] userMailAccountService.ts (DB CRUD)
- [x] userMailImapService.ts (IMAP 프로토콜)
- [x] userMailSmtpService.ts (SMTP 발송)
- [x] imapConnectionPool.ts (IMAP 연결 풀)
- [x] mailCache.ts (메일 캐시)
- [ ] userMailPop3Service.ts (POP3 프로토콜 - 미구현)
### 백엔드 API 계층
- [x] userMailController.ts (요청 처리)
- [x] userMailRoutes.ts (라우트 정의)
- [x] app.ts에 userMailRoutes 등록 (`/api/user-mail`)
### 프론트엔드 API 클라이언트
- [x] frontend/lib/api/userMail.ts
### 프론트엔드 페이지
- [x] frontend/app/(main)/mail/imap/page.tsx
- [x] frontend/app/(main)/mail/imap/ComposeDialog.tsx (메일 작성)
- [ ] frontend/app/(main)/mail/pop3/page.tsx (미구현)
### 페이지 등록
- [x] AdminPageRenderer.tsx에 /mail/imap 등록
- [ ] AdminPageRenderer.tsx에 /mail/pop3 등록 (미구현)
---
## 구현된 IMAP 기능
### 계정 관리
- [x] 계정 추가 (연결 테스트 후 저장)
- [x] 계정 수정
- [x] 계정 삭제
- [x] 연결 테스트 (저장 전 자동 + 수동)
### 메일 조회
- [x] SSE 스트리밍으로 메일 목록 로드 (20개씩)
- [x] 이전 메일 더 보기 (무한 스크롤 방식)
- [x] 메일 상세 조회 (HTML/텍스트 본문)
- [x] 폴더별 메일 조회 (INBOX, 휴지통, 스팸 등)
- [x] 새로고침 버튼
### 메일 관리
- [x] 읽음 처리 (클릭 시 자동, IMAP \Seen 플래그)
- [x] 메일 삭제 (\Trash 특수 폴더로 이동 - Gmail 호환)
- [x] 메일 이동 (폴더 간 이동)
### 첨부파일
- [x] 첨부파일 목록 표시 (pill 형태)
- [x] 첨부파일 다운로드 (ReadableStream 진행바 표시)
- [x] Content-Length 헤더 지원 (정확한 진행률)
### 발신
- [x] 메일 작성 / 발송 (SMTP)
- [x] 답장 (Re: 제목, inReplyTo 헤더)
- [x] 전달 (Fwd: 제목, 원본 본문 인용)
### UI
- [x] 3단 패널 레이아웃 (계정 / 메일 목록 / 상세)
- [x] 폴더 목록 (unseen 카운트 표시)
- [x] 읽음/삭제 후 unseen 카운트 자동 갱신
- [x] 검색 (제목/발신자 클라이언트 필터)
---
## 검증 체크리스트
### 데이터베이스
- [x] `user_mail_accounts` 테이블 존재 확인
- [x] 테이블 스키마 정확성 확인
### 계정 관리 API
- [x] POST `/api/user-mail/accounts` - 계정 생성
- [x] GET `/api/user-mail/accounts` - 사용자 계정 목록
- [x] PUT `/api/user-mail/accounts/:id` - 계정 수정
- [x] DELETE `/api/user-mail/accounts/:id` - 계정 삭제
- [x] POST `/api/user-mail/accounts/:id/test` - 연결 테스트
- [x] POST `/api/user-mail/test-connection` - 직접 연결 테스트
### 메일 API
- [x] GET `/api/user-mail/accounts/:id/mails/stream` - 스트리밍 목록
- [x] GET `/api/user-mail/accounts/:id/mails/:seqno` - 상세 조회
- [x] POST `/api/user-mail/accounts/:id/mails/:seqno/mark-read` - 읽음 처리
- [x] DELETE `/api/user-mail/accounts/:id/mails/:seqno` - 삭제 (휴지통 이동)
- [x] POST `/api/user-mail/accounts/:id/mails/:seqno/move` - 이동
- [x] GET `/api/user-mail/accounts/:id/folders` - 폴더 목록
- [x] GET `/api/user-mail/accounts/:id/folders/:folder/mails/stream` - 폴더별 스트리밍
- [x] GET `/api/user-mail/accounts/:id/mails/:seqno/attachments` - 첨부파일 목록
- [x] GET `/api/user-mail/accounts/:id/mails/:seqno/attachment/:partId` - 첨부파일 다운로드
- [x] POST `/api/user-mail/accounts/:id/send` - 메일 발송
### 사용자 격리 검증
- [x] 모든 쿼리에 WHERE user_id = $n 포함 (DB 레벨 강제)
- [x] 다른 user_id로 계정 접근 시 404 반환
### 프론트엔드 페이지
- [x] `/mail/imap` 페이지 접속 및 동작
- [x] Gmail IMAP 연동 확인
- [x] 메일 목록 → 상세 → 읽음 처리
- [x] 첨부파일 다운로드 진행바
- [x] 메일 삭제 → Gmail 휴지통 이동 확인
- [x] 답장/전달 발송 확인
---
## 알려진 이슈 및 주의사항
### 1. 메일 삭제 방식
- `\Trash` 특수 폴더로 이동 (EXPUNGE 아님)
- Gmail 호환: `[Gmail]/휴지통`으로 자동 라우팅
- 폴더 없으면 `messageDelete` fallback (영구 삭제 주의)
### 2. 첨부파일 진행바
- Content-Length 헤더 기반 진행률 계산
- imapflow `meta.size`로 헤더 설정
- totalSize fallback: `getUserMailAttachments`의 size 필드 사용
### 3. IMAP 연결 풀
- 계정당 1개 연결 유지 (maxIdleMs: 5분)
- busy 상태 시 큐잉 처리
- 연결 끊김 시 자동 재연결
### 4. 캐시
- 메일 목록: 60초 TTL
- 메일 상세: 5분 TTL
- 읽음/삭제/이동 시 해당 캐시 무효화
### 5. POP3 미구현
- `node-pop3` 패키지 설치됨
- 서비스 파일 미작성
- 팀장 지시 후 구현 예정
---
## 변경 이력
| 날짜 | 버전 | 내용 |
|------|------|------|
| 2026-03-27 | v1.0 | 초안 작성 |
| 2026-03-30 | v2.0 | IMAP 전 기능 구현 완료 (메일 조회/삭제/이동/첨부/발송/답장/전달/폴더/진행바) |
---
## 관련 문서
- [UML[계획]-user-mail.md](./UML[계획]-user-mail.md): 아키텍처 및 설계