# 피드백 작성 페이지와 관리페이지 데이터 흐름 ## 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`