118 lines
5.5 KiB
Markdown
118 lines
5.5 KiB
Markdown
# EG-BIM Q&A 피드백 저장 API 연동 Task
|
|
|
|
기준일: 2026-09-21
|
|
대상: `qa-test.baroncs.co.kr` / Cloudflare Worker `baron-qa-gateway-test`
|
|
|
|
## 목표
|
|
|
|
작성페이지의 문의를 Cloudflare Worker를 통해 ABC UserFeedback API에 저장한다.
|
|
|
|
```text
|
|
작성페이지
|
|
→ Worker POST
|
|
→ Q&A SSO 세션 확인
|
|
→ Worker가 requester 정보와 API Key 추가
|
|
→ ABC UserFeedback API
|
|
→ feedbacks 저장
|
|
→ { id } 반환
|
|
→ 상세 페이지 이동
|
|
```
|
|
|
|
## 확정 설정
|
|
|
|
| 항목 | 값 |
|
|
|---|---|
|
|
| Worker | `baron-qa-gateway-test` |
|
|
| 작성페이지 | `https://qa-test.baroncs.co.kr` |
|
|
| ABC API | `https://feedback.hmac.kr` |
|
|
| projectId | `01a0ae3f-fcf6-74b5-bdc4-70d942d6ad72` |
|
|
| channelId | `01a0ae40-51c2-7647-a93c-0249b3759777` |
|
|
| source namespace | `EGBIM_QA` |
|
|
| API Key Secret | `ABC_API_KEY` |
|
|
|
|
## 1차 범위: 텍스트 문의 저장
|
|
|
|
- [x] 프로젝트·채널 UUID 확인
|
|
- [x] 채널 필드 확인: `title`, `contents`, `Category`, `images`
|
|
- [x] 서버 requester 메타데이터 계약 확인
|
|
- [ ] `requester_uuid`, `requester_affiliation`, `requester_position`, `requester_grade`의 공개 생성 API 지원 확인
|
|
- [x] SSO 세션 저장 및 `/auth/session` 확인
|
|
- [x] Worker에 `POST /api/feedbacks` 라우트 추가
|
|
- [x] Worker에서 `baron_qa_session` 검증
|
|
- [x] Worker에서 requester 정보 추출
|
|
- [x] `requester_id` 매핑: SSO `sub`
|
|
- [x] `requester_tenant_id` 매핑: SSO `tenant_id`
|
|
- [x] `requester_name` 매핑: SSO profile/name
|
|
- [x] `requester_email` 매핑: SSO profile/email
|
|
- [x] `requester_department` 매핑: SSO tenant/profile department
|
|
- [x] `requester_phone_number` 매핑: SSO `profile.phones[0]` 또는 phone claim
|
|
- [x] 브라우저 요청의 requester 값을 신뢰하지 않도록 처리
|
|
- [x] Worker Secret `ABC_API_KEY`로 `x-api-key` 추가
|
|
- [x] `projectId`와 `channelId`를 Worker 설정에 등록
|
|
- [x] `POST /api/projects/{projectId}/channels/{channelId}/feedbacks` 호출
|
|
- [x] `x-source-namespace` 추가
|
|
- [x] `x-source-record-id` 추가
|
|
- [x] `x-idempotency-consumer` 추가
|
|
- [x] `Idempotency-Key`와 `idempotency-key` 호환 처리
|
|
- [x] 응답에서 feedback ID를 추출해 `{ id }` 형식으로 반환
|
|
- [x] API 실패 시 ABC 오류를 노출하지 않고 안전한 오류 응답 반환
|
|
- [x] 성공 시 작성페이지에서 `detail.html?id={id}`로 이동
|
|
|
|
## 2차 범위: 첨부파일
|
|
|
|
- [x] 작성페이지에서 `images` multipart 바이너리 전송
|
|
- [x] Worker가 SSO requester 메타데이터와 `images`를 ABC API로 전달
|
|
- [x] ABC API가 허용하는 이미지 포함 multipart 계약 확인
|
|
- [x] 파일당 30MB 제한 및 첨부파일 오류 처리
|
|
- [ ] 이미지 포함 실제 저장 테스트
|
|
- [ ] ABC 상세 조회 응답의 첨부파일 다운로드 URL 연결
|
|
|
|
## 3차 범위: 공개 댓글
|
|
|
|
- [x] 상세페이지 댓글 입력 UI 추가
|
|
- [x] Worker 댓글 목록 조회 라우트 추가
|
|
- [x] Worker 공개 댓글 생성 라우트 추가
|
|
- [x] `is_internal=false` 공개 댓글만 조회
|
|
- [ ] 실제 관리자 답변 등록 후 Q&A 상세페이지 표시 테스트
|
|
|
|
## 보안 요구사항
|
|
|
|
- [x] API Key를 정적 JavaScript, HTML, `egbim/config.js`에 넣지 않음
|
|
- [x] API Key는 Cloudflare Worker Secret에만 저장
|
|
- [x] requester 정보는 브라우저 입력값이 아닌 검증된 SSO 세션에서 생성
|
|
- [x] 전화번호는 화면 입력값을 받지 않고 SSO 프로필에서만 읽음
|
|
- [x] Worker 로그에 API Key, SSO token, 전화번호 원문을 기록하지 않음
|
|
- [x] CORS는 동일 Worker 도메인 요청을 기준으로 제한
|
|
|
|
## 검증 시나리오
|
|
|
|
- [ ] 로그인하지 않은 사용자는 `401` 응답을 받음
|
|
- [ ] 로그인한 사용자가 제목·내용·카테고리를 입력하면 ABC에 1건 저장됨
|
|
- [ ] 저장된 데이터에 제목·내용·Category가 정확히 들어감
|
|
- [ ] 저장된 데이터에 requester ID·tenant·이름·이메일·부서·전화번호가 들어감
|
|
- [ ] 서버가 지원하는 경우 requester UUID·소속·직책·등급도 저장됨
|
|
- [ ] 동일한 `Idempotency-Key` 재요청 시 중복 저장되지 않음
|
|
- [ ] ABC 응답의 `id`로 상세 페이지 이동
|
|
- [ ] API Key가 브라우저 Network 탭에 노출되지 않음
|
|
- [ ] 잘못된 카테고리 또는 필드 입력은 ABC에 전달되기 전에 차단됨
|
|
|
|
## 배포 전 작업
|
|
|
|
- [ ] 실제 프로젝트 전용 ABC API Key 발급
|
|
- [ ] Cloudflare Worker Secret 등록
|
|
|
|
```bash
|
|
npx wrangler secret put ABC_API_KEY --name baron-qa-gateway-test
|
|
```
|
|
|
|
- [ ] `qa-test.baroncs.co.kr`에서 로그인 후 텍스트 문의 1건 등록
|
|
- [ ] Worker Logs에서 API Key·전화번호가 노출되지 않는지 확인
|
|
- [ ] ABC 관리페이지에서 requester 전화번호를 포함한 저장 결과 확인
|
|
|
|
## 구현 메모
|
|
|
|
- ABC 문서에는 현재 `/api/projects/...` 경로가 실제 시나리오로 기재되어 있다.
|
|
- `/api/v1/...`는 패키징 권장 경로로 문서화되어 있으므로 1차 구현은 현재 운영 시나리오인 `/api/...`를 사용한다.
|
|
- UUID v7 정책에 맞춰 현재 브라우저의 UUID v4 생성 로직을 교체한다.
|
|
- 현재 공개 생성 API에서 실제로 확인된 메타데이터는 `requester_id`, `requester_tenant_id`, `requester_email`, `requester_name`, `requester_department`, `requester_phone_number`, `is_secret`다. 모든 값은 문자열로 전송하며 `is_secret`은 반드시 `"true"` 또는 `"false"`를 사용한다. `requester_affiliation`은 실제 요청에서 `invalid field key`로 거부되어 1차 저장 payload에서 제외했다. 나머지 확장 메타데이터는 서버 endpoint 지원 여부를 확인한 뒤 다시 추가한다.
|