# 피드백 작성 페이지와 관리페이지 데이터 흐름
## 1. 전체 구조
현재 구조는 피드백 작성 웹과 관리 콘솔이 분리되어 있습니다. 작성 웹은 직접 ABC API나 DB에 접근하지 않고, Next.js 서버 Proxy를 통해 외부 관리 콘솔 API에 요청합니다.
```text
피드백 작성 페이지
/support/{workspaceCode}/new
│
│ POST /api/support/workspaces/{workspaceCode}/submit
▼
apps/web의 Next.js API Proxy
│
│ POST /api/support/workspaces/{workspaceCode}/tickets
▼
외부 관리 콘솔 API
https://feedback.hmac.kr/api/support
│
├─ Secretary DB: support_tickets 저장
├─ ABC API: feedbacks 저장
├─ abc_feedback_mappings: 두 ID 연결
└─ 첨부파일: Secretary Local/R2 저장
▼
관리 콘솔에서 조회·처리
```
피드백 전용 배포에서는 브라우저 요청을 same-origin으로 유지하고, 외부 관리 콘솔 주소는 서버 환경변수로만 사용합니다.
관련 설정:
- `NEXT_PUBLIC_FEEDBACK_ONLY=true`
- `SUPPORT_CONSOLE_API_BASE_URL=https://feedback.hmac.kr/api/support`
- `NEXT_PUBLIC_API_BASE_URL`은 빈 값으로 설정
## 1.1 전체 데이터 흐름도
```mermaid
flowchart LR
U[사용자] --> W[피드백 작성 페이지
/support/{workspaceCode}/new]
W -->|FormData
제목·내용·필드·첨부파일| P[Next.js 서버 Proxy]
P -->|POST /api/support/workspaces/{workspaceCode}/tickets| S[외부 관리 콘솔 API
Secretary]
S -->|1. 내부 티켓 저장| T[(Secretary DB
baron_support)]
S -->|2. ABC 피드백 생성| A[ABC API]
A --> F[(ABC MySQL
feedbacks)]
S --> M[(abc_feedback_mappings)]
M -. ticket_id ↔ feedback_id .-> T
M -. ticket_id ↔ feedback_id .-> F
S -->|첨부파일 저장| X[(Secretary Local/R2
+ attachments 메타데이터)]
T --> C[관리 콘솔 목록·상세 화면]
F --> C
X --> C
```
### 핵심 저장 위치
```mermaid
flowchart TD
D[피드백 등록 데이터]
D --> D1[ABC MySQL
feedbacks.data JSON
원본 피드백 본문]
D --> D2[Secretary DB
support_tickets
관리용 티켓·상태·권한]
D --> D3[Secretary DB
abc_feedback_mappings
두 시스템 ID 연결]
D --> D4[Secretary Local/R2
첨부파일 바이너리]
D --> D5[Secretary DB
attachments
첨부파일 메타데이터]
D1 -. 검색 활성화 시 .-> D6[OpenSearch
검색용 색인]
```
### ID 연결 구조
```mermaid
flowchart LR
W[workspace_code
예: EGBIM_DEMO]
W --> ST[Secretary ticket_id]
ST --> MAP[abc_feedback_mappings]
MAP --> AF[ABC feedback_id]
AF --> CH[ABC project_id / channel_id]
```
`ticket_id`와 `feedback_id`는 서로 다른 시스템에서 생성되는 별도 ID입니다. 관리 콘솔은 `abc_feedback_mappings` 또는 응답의 `extra_fields.abc_feedback_id`를 이용해 두 데이터를 결합합니다.
## 1.2 네이버웍스 알림 흐름도
피드백 등록 자체는 먼저 ABC와 Secretary에 저장되고, 저장 완료 후 ABC 백엔드 이벤트를 통해 네이버웍스 알림이 발송됩니다.
```mermaid
sequenceDiagram
participant User as 사용자
participant Web as 작성 웹
participant Secretary as Secretary API
participant ABC as ABC API
participant ABCDB as ABC MySQL
participant Notify as 알림 서비스
participant NW as 네이버웍스 API
participant Log as notification_deliveries
User->>Web: 피드백 등록
Web->>Secretary: POST /workspaces/{code}/tickets
Secretary->>Secretary: support_tickets 저장
Secretary->>ABC: feedback 생성 요청
ABC->>ABCDB: feedbacks 저장
ABC-->>Notify: FEEDBACK_CREATION 이벤트
Notify->>Notify: 담당자·프로젝트 관리자·작성자 계산
Notify->>NW: 이메일로 사용자 ID 조회
NW-->>Notify: 네이버웍스 userId
Notify->>NW: Bot 개인 메시지 발송
Notify->>Log: SENT / FAILED / SKIPPED 기록
ABC-->>Secretary: abc_feedback_id 반환
Secretary-->>Web: ticket_id·동기화 상태 반환
```
## 2. 피드백 작성 페이지
작성 페이지는 다음 파일입니다.
`apps/web/src/pages/support/[workspaceCode]/new.tsx`
### 양식 조회
페이지 진입 시 workspace별 양식을 조회합니다.
```text
GET /api/support/workspaces/{workspaceCode}/form-template
```
Next.js API Proxy는 이를 외부 관리 콘솔 API로 전달합니다.
```text
GET {SUPPORT_CONSOLE_API_BASE_URL}/workspaces/{workspaceCode}/form-template
```
양식의 workspace code는 프로젝트와 ABC 프로젝트·채널을 연결하는 식별자입니다.
### 등록 요청
사용자가 등록하면 브라우저는 `FormData`를 생성합니다.
주요 필드는 다음과 같습니다.
- `title`
- `description`
- `category_code`
- `is_secret`
- `ticket_type`
- `requires_approval`
- `extra_fields`
- `attachments`
작성 페이지는 다음 내부 API를 호출합니다.
```text
POST /api/support/workspaces/{workspaceCode}/submit
```
관련 파일:
`apps/web/src/pages/api/support/workspaces/[workspaceCode]/submit.ts`
이 API는 multipart 요청을 파싱한 후 외부 관리 콘솔로 다시 전달합니다.
```text
POST {SUPPORT_CONSOLE_API_BASE_URL}/workspaces/{workspaceCode}/tickets
```
인증 정보는 브라우저에서 별도 입력받지 않고, 현재 로그인된 BARON-SSO 세션의 Authorization 헤더를 Proxy가 전달합니다.
## 3. Secretary API에서의 저장
Secretary API의 티켓 생성 로직은 다음 파일에 있습니다.
`apps/secretary-api/app/services/ticket_service.py`
티켓 등록 시 다음 순서로 처리됩니다.
1. `support_tickets`에 내부 관리 티켓을 생성합니다.
2. workspace에 매핑된 ABC 프로젝트와 채널을 확인합니다.
3. ABC API에 피드백을 생성합니다.
4. `abc_feedback_mappings`에 Secretary 티켓과 ABC 피드백의 연결 정보를 저장합니다.
5. 첨부파일을 Secretary 저장소에 저장합니다.
6. 동기화 상태를 `SYNCED`로 변경합니다.
7. 티켓 ID와 동기화 상태를 작성 웹에 반환합니다.
API 라우트:
```text
POST /api/workspaces/{workspaceCode}/tickets
```
구현 파일:
`apps/secretary-api/app/api/routes/tickets.py`
## 4. 데이터베이스 구조
### Secretary DB
Secretary 쪽에는 관리 업무용 데이터가 저장됩니다.
| 테이블 | 역할 |
| --- | --- |
| `support_tickets` | 내부 관리 티켓 본체 |
| `abc_feedback_mappings` | Secretary 티켓 ID와 ABC 피드백 ID 연결 |
| `ticket_comments` | 사용자 댓글 및 내부 메모 |
| `attachments` | 첨부파일 메타데이터 |
| `user_workspace_access` | 사용자별 workspace 권한 |
주요 모델은 다음 파일에 정의되어 있습니다.
`apps/secretary-api/app/db/models.py`
### ABC DB
ABC 쪽에는 기존 피드백 데이터가 저장됩니다.
| 테이블 | 역할 |
| --- | --- |
| `feedbacks` | 피드백 본문과 동적 필드를 `data` JSON으로 저장 |
| `channels` | 피드백이 소속된 채널 |
| `feedbacks_issues_issues` | 피드백과 이슈의 다대다 연결 |
ABC 피드백 엔티티:
`apps/api/src/domains/admin/feedback/feedback.entity.ts`
## 5. 티켓 ID와 피드백 ID
Secretary 티켓 ID와 ABC 피드백 ID는 서로 다른 식별자입니다.
```text
Secretary ticket_id = 관리 티켓 식별자
ABC feedback_id = ABC 피드백 식별자
abc_feedback_mappings = 두 식별자를 연결하는 매핑
```
관리 API 응답에서는 보통 다음과 같이 ABC ID가 함께 노출됩니다.
```json
{
"ticket_id": 101,
"extra_fields": {
"abc_feedback_id": "345"
}
}
```
이 매핑을 이용해 관리 콘솔은 티켓 정보와 ABC 피드백 정보, 이슈 연결 정보를 함께 처리합니다.
## 6. 관리페이지 조회 흐름
실제 운영 관리 콘솔은 Secretary API를 기준으로 티켓 목록을 조회합니다.
```text
GET /api/support/workspaces/{workspaceCode}/tickets
│
├─ support_tickets 조회
├─ abc_feedback_mappings 확인
├─ 담당자·권한 정보 결합
├─ 댓글·첨부파일 정보 결합
└─ ABC 피드백과 상태 동기화
```
피드백 전용 모드에서 이 저장소의 목록 Proxy는 외부 관리 콘솔 API를 그대로 호출합니다.
관련 파일:
`apps/web/src/pages/api/support/workspaces/[workspaceCode]/tickets.ts`
관리 콘솔에서 처리하는 주요 데이터는 다음과 같습니다.
- 피드백 목록과 상세 내용
- 피드백 처리 상태
- 담당자 지정
- 사용자 댓글
- 내부 메모
- 첨부파일
- ABC 이슈 연결 상태
## 7. 이 저장소에 있는 기존 ABC 관리자 화면
이 저장소에는 기존 ABC 관리자 화면도 남아 있습니다.
```text
/main/project/{projectId}/feedback
```
페이지:
`apps/web/src/pages/main/project/[projectId]/feedback.tsx`
이 화면은 Secretary 티켓 목록이 아니라 ABC 관리자 API를 직접 조회합니다.
```text
POST /api/admin/projects/{projectId}/channels/{channelId}/feedbacks/search
```
ABC API의 검색 요청은 다음 서버 로직으로 처리됩니다.
`apps/api/src/domains/admin/feedback/feedback.service.ts`
검색 데이터는 설정에 따라 다음 중 하나에서 조회됩니다.
- ABC MySQL의 `feedbacks` 테이블
- OpenSearch의 피드백 인덱스
따라서 이 화면은 실제 외부 관리 콘솔의 Secretary 기반 관리 화면과 데이터 조회 기준이 다를 수 있습니다.
## 8. 화면별 역할 비교
| 화면 | 주요 데이터 원천 | 역할 |
| --- | --- | --- |
| `/support/{workspaceCode}/new` | 외부 관리 콘솔 등록 API | 피드백 작성 |
| 외부 관리 콘솔 `feedback.hmac.kr` | Secretary + ABC | 실제 운영 관리 |
| `/main/project/{projectId}/feedback` | ABC API 직접 조회 | 기존 ABC 관리자 화면 |
| `/admin/issues` | Secretary 티켓 API 또는 스텁 | 데모·보조 관리 화면 |
| `/ops` | Secretary 티켓 API 또는 스텁 | 승인 큐 데모 화면 |
피드백 전용 전환 문서의 목표는 이 저장소에서 작성 화면만 제공하고, 목록·상세·관리 기능은 외부 관리 콘솔에서 유지하는 것입니다.
## 9. 첨부파일 흐름
작성 페이지에서 첨부파일을 선택하면 브라우저의 `FormData`에 포함됩니다.
```text
브라우저
→ Next.js Proxy
→ 외부 관리 콘솔/Secretary API
→ Secretary Local 또는 R2 저장소
→ attachments 테이블에 메타데이터 저장
```
현재 작성 화면의 제한은 다음과 같습니다.
- 파일당 최대 30MB
- 전체 최대 30MB
- 최대 10개
- 이미지 외 일반 파일도 업로드 가능
첨부파일은 피드백 본문 JSON에 직접 저장되지 않고 별도 저장소와 `attachments` 메타데이터로 관리됩니다.
## 10. 네이버웍스 알림 흐름
피드백 작성 웹은 네이버웍스 API를 직접 호출하지 않습니다. 피드백 등록 후 ABC API와 기존 관리 콘솔의 백엔드 알림 파이프라인에서 네이버웍스 알림을 처리합니다.
```text
피드백 작성
→ Secretary가 내부 티켓 저장
→ ABC API가 feedbacks 저장
→ FEEDBACK_CREATION 이벤트 발생
→ 알림 수신자 계산
→ 네이버웍스 사용자 ID 조회
→ 네이버웍스 Bot 메시지 발송
→ notification_deliveries에 결과 저장
```
### 10.1 알림 이벤트 발생
ABC의 [`FeedbackService.create`](apps/api/src/domains/admin/feedback/feedback.service.ts)는 피드백 저장 후 `FEEDBACK_CREATION` 이벤트를 발생시킵니다.
```text
apps/api/src/domains/admin/feedback/feedback.service.ts
│
│ EventTypeEnum.FEEDBACK_CREATION
▼
apps/api/src/domains/admin/project/webhook/notification-internal.listener.ts
```
트랜잭션 커밋 전에 알림이 조회되지 않도록, 생성 이벤트 처리 시 저장 완료 후 피드백을 다시 조회합니다.
알림 이벤트에는 다음 정보가 포함됩니다.
- `projectId`
- `channelId`
- `feedbackId`
- 피드백 제목
- 작성자 이메일
- 담당자 이메일
- 현재 피드백 상태
- 비밀글 여부
### 10.2 알림 대상
수신자 계산은 [`notification-recipient.router.ts`](apps/api/src/domains/admin/project/webhook/notification-recipient.router.ts)에서 수행합니다.
신규 피드백과 상태 변경 시 대상은 다음과 같습니다.
- 해당 피드백의 담당자
- 해당 프로젝트의 `PROJECT_MANAGER` 또는 `Admin` 역할 사용자
- 피드백 작성자
관리자 댓글 등록 시에는 해당 피드백 작성자에게만 보냅니다.
같은 사용자가 여러 역할에 해당하면 이메일 기준으로 중복 제거하여 한 번만 발송합니다. 프로젝트 관리자는 ABC의 프로젝트 멤버와 Secretary의 관리자 조회 결과를 함께 사용합니다.
```text
ABC project/channel
│
├─ ABC DB의 프로젝트 관리자 조회
├─ Secretary API의 프로젝트 관리자 조회
├─ 피드백 담당자 추가
└─ 피드백 작성자 추가
│
▼
이메일 기준 중복 제거
```
수신자 이메일은 네이버웍스 API에서 실제 사용자 ID로 변환됩니다. 이메일 또는 사용자 매핑이 확인되지 않으면 임의 사용자나 전체 방으로 대체 발송하지 않습니다.
### 10.3 네이버웍스 발송
실제 발송은 [`NaverWorksClient`](apps/api/src/domains/admin/project/webhook/naver-works.client.ts)에서 수행합니다.
1. 네이버웍스 Access Token을 확인합니다.
2. 설정된 이메일로 네이버웍스 사용자 ID를 조회합니다.
3. Bot ID와 사용자 ID를 사용해 개인 메시지를 발송합니다.
4. 발송 성공·실패 결과를 저장합니다.
인증 방식은 두 가지를 지원합니다.
- `NAVER_WORKS_ACCESS_TOKEN` 직접 사용
- Client ID, Client Secret, Service Account, Private Key를 이용한 JWT Bearer 인증
관련 환경변수는 API 서버에만 설정해야 합니다.
```text
NAVER_WORKS_ENABLED
NAVER_WORKS_API_BASE_URL
NAVER_WORKS_AUTH_URL
NAVER_WORKS_ACCESS_TOKEN
NAVER_WORKS_BOT_ID
NAVER_WORKS_DEFAULT_ROOM_ID
NAVER_WORKS_CLIENT_ID
NAVER_WORKS_CLIENT_SECRET
NAVER_WORKS_SERVICE_ACCOUNT
NAVER_WORKS_PRIVATE_KEY
```
피드백 전용 작성 웹에는 이 값들을 넣지 않습니다.
### 10.4 메시지 구성
메시지는 [`notification-message.builder.ts`](apps/api/src/domains/admin/project/webhook/notification-message.builder.ts)에서 생성합니다.
기본 메시지에는 다음 정보가 포함됩니다.
- 이벤트 종류
- 피드백 ID
- 피드백 제목
- 상태 변경 전·후 상태
- 관리자 공개 댓글 내용
- 기존 관리자 상세 페이지 링크
비밀글 작성자에게 보내는 메시지는 제목·본문·댓글 원문을 노출하지 않고 피드백 ID 중심으로 최소화합니다. 전화번호, IP 주소, MAC 주소는 기본 알림에 포함하지 않습니다.
### 10.5 알림 대상 이벤트
| 이벤트 | 기본 수신자 | 조건 |
| --- | --- | --- |
| `FEEDBACK_CREATION` | 담당자, 프로젝트 관리자, 작성자 | 신규 피드백 등록 |
| `FEEDBACK_STATUS_CHANGE` | 담당자, 프로젝트 관리자, 작성자 | 이전 상태와 현재 상태가 다름 |
| `FEEDBACK_COMMENT_CREATION` | 작성자 | 관리자의 공개 댓글 등록 |
다음 이벤트는 현재 네이버웍스 피드백 알림 대상이 아닙니다.
- 내부 메모 등록·수정·삭제
- 관리자 공개 댓글 수정·삭제
- 이슈 상태 변경
- Gitea 상태 변경
- 같은 상태로 다시 저장한 상태 변경
### 10.6 중복 및 실패 처리
알림 서비스는 `notification_deliveries` 테이블에 이벤트별 발송 결과를 기록합니다.
주요 상태:
- `RECEIVED`: 발송 처리 대상 등록
- `SENT`: 발송 성공
- `SKIPPED`: 대상 없음 또는 알림 제외
- `FAILED`: 발송 실패
이벤트와 수신자 조합으로 Idempotency Key를 생성하므로 동일 이벤트가 반복 수신되어도 같은 사용자에게 중복 발송하지 않습니다.
네이버웍스 발송은 네트워크 오류, `408`, `429`, `5xx` 응답에 대해 제한적으로 재시도합니다. `400`, `403`, 사용자 매핑 실패와 같은 영구 오류는 반복하지 않고 실패 이력으로 남깁니다.
발송 실패는 피드백 등록 실패와 분리됩니다. 즉, 피드백이 ABC와 Secretary에 정상 저장된 뒤 네이버웍스 알림만 실패할 수 있습니다.
## 11. 현재 운영 기준 요약
```text
사용자 입력
→ /support/{workspaceCode}/new
→ /api/support/workspaces/{workspaceCode}/submit
→ 외부 관리 콘솔 API
→ Secretary support_tickets 저장
→ ABC feedbacks 저장
→ abc_feedback_mappings로 연결
→ 외부 관리 콘솔에서 조회·상태변경·댓글·이슈처리
```
정리하면 작성 페이지와 관리페이지는 프론트엔드 상태를 직접 공유하지 않습니다. 두 시스템은 다음 정보를 기준으로 서버 데이터에서 연결됩니다.
```text
workspace_code
→ Secretary ticket_id
→ abc_feedback_id
→ ABC project/channel
```
## 참고 파일
- `FEEDBACK_ONLY_TASKS.md`
- `apps/web/src/pages/support/[workspaceCode]/new.tsx`
- `apps/web/src/pages/api/support/workspaces/[workspaceCode]/submit.ts`
- `apps/web/src/pages/api/support/workspaces/[workspaceCode]/tickets.ts`
- `apps/web/src/server/support-external.ts`
- `apps/web/src/server/support-auth.ts`
- `apps/secretary-api/app/api/routes/tickets.py`
- `apps/secretary-api/app/services/ticket_service.py`
- `apps/secretary-api/app/db/models.py`
- `apps/api/src/domains/admin/feedback/feedback.entity.ts`
- `apps/api/src/domains/admin/feedback/feedback.service.ts`
- `apps/api/src/domains/admin/project/webhook/notification-internal.listener.ts`
- `apps/api/src/domains/admin/project/webhook/notification-recipient.router.ts`
- `apps/api/src/domains/admin/project/webhook/webhook-notification.service.ts`
- `apps/api/src/domains/admin/project/webhook/notification-message.builder.ts`
- `apps/api/src/domains/admin/project/webhook/naver-works.client.ts`
- `apps/api/src/domains/admin/project/webhook/notification-delivery.entity.ts`