feat(ui): apply inquiry list and modal design
Apply the supplied inquiry layout to the list and native write/detail dialogs while preserving existing API requests, author controls, attachments, comments and search. Keep legacy entry links and confirm before discarding drafts. Validation: 98 tests and syntax checks pass; mocked Chromium desktop/mobile and six author/session visibility scenarios pass.
This commit is contained in:
1 parent
dbf5dc3611
commit
c62630e22d
19 files changed
+1362
-277
No files matched your search
@@ -1,165 +1,83 @@
|
||||
# EG-BIM Q&A 로그인부터 글 저장까지 전이맵
|
||||
# EG-BIM Q&A 로그인·화면·저장 흐름
|
||||
|
||||
기준일: 2026-09-21
|
||||
대상: `qa-test.baroncs.co.kr` / EG-BIM Q&A
|
||||
구성: 목록(`index.html`) · 작성(`write.html`) · 상세(`detail.html`)
|
||||
기준일: 2026-11-02. 스테이징 `qa-test.baroncs.co.kr`, 운영 `qa.baroncs.co.kr`의 현재 구조를 설명한다. 이번 UI 개편은 프론트에 한정하며 Worker API·DB·SSO 설정을 변경하지 않는다.
|
||||
|
||||
## 1. 전체 흐름
|
||||
## 로그인과 화면 진입
|
||||
|
||||
홈페이지 로그인 정보를 Q&A 사이트로 직접 전달하지 않는다. 홈페이지의 Q&A 링크는 별도 호스트로 이동하고, Q&A는 같은 BRSW OIDC SSO에 직접 인증을 요청한다. 이미 중앙 SSO 세션이 있으면 사용자는 로그인 화면을 보지 않고 Q&A 세션을 발급받는다.
|
||||
Q&A는 기존 Worker의 OIDC Authorization Code + PKCE와 HttpOnly 세션을 사용한다. 홈페이지에서 토큰을 전달하지 않는다. 보호된 페이지에 세션 없이 접근하면 Worker가 `/auth/login?return_url=...`로 보내며, 인증 후 원래 경로와 쿼리로 돌아온다. 작성자와 회사·부서는 `GET /auth/session`의 검증된 정보로 표시한다.
|
||||
|
||||
| 주소·사용자 동작 | 화면 |
|
||||
| --- | --- |
|
||||
| `/egbim/`, `/egbim/index.html` | 문의 목록 |
|
||||
| 문의등록 / `?view=write` | 목록 위 작성 모달 |
|
||||
| 글 제목 / `?view=detail&id=<UUID>` | 목록 위 해당 글 상세 모달 |
|
||||
| `/egbim/write.html` | 프론트에서 `/egbim/?view=write`로 replace 이동 |
|
||||
| `/egbim/detail.html?id=<UUID>` | 프론트에서 `/egbim/?view=detail&id=<UUID>`로 replace 이동 |
|
||||
|
||||
구형 주소도 Worker의 기존 로그인 보호를 거친다. 새로고침과 직접 링크는 URL에 지정한 모달을 복원한다. 모달의 열기·닫기는 브라우저 뒤로·앞으로 이동과 연결한다. 목록 위에서 열었을 때 검색어·분류·내 글·페이지·스크롤을 유지한다. 작성·수정·댓글 초안이나 파일 선택 변경이 있으면 닫기 전에 폐기를 확인한다.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[바론 홈페이지<br/>EG-BIM Q&A 링크] --> B[Q&A 목록 진입<br/>https://qa-test.baroncs.co.kr/egbim/]
|
||||
B --> C{Q&A 세션 쿠키<br/>baron_qa_session_v2 유효?}
|
||||
|
||||
C -- 예 --> G[정적 목록 페이지 표시]
|
||||
C -- 아니오 --> D[302 /auth/login<br/>return_url 보관]
|
||||
D --> E[Worker가 PKCE 생성<br/>state · verifier · nonce]
|
||||
E --> F[BARON-SSO OIDC<br/>sso.hmac.kr/oidc]
|
||||
F --> F1{중앙 SSO 세션 존재?}
|
||||
F1 -- 아니오 --> F2[SSO 로그인 화면]
|
||||
F2 --> F3[최초 동의 화면<br/>필요 시 1회]
|
||||
F1 -- 예 --> H[승인 코드 발급]
|
||||
F3 --> H
|
||||
H --> I[/auth/callback?code&state]
|
||||
I --> J[Worker가 state 검증<br/>PKCE code_verifier 검증]
|
||||
J --> K[Token Endpoint 호출<br/>Public Client · client_secret 없음]
|
||||
K --> L[UserInfo 조회 및 claim 정규화]
|
||||
L --> M[baron_qa_session_v2 발급<br/>HttpOnly · Secure · SameSite=Lax]
|
||||
M --> G
|
||||
|
||||
G --> N[문의 등록 버튼]
|
||||
N --> O[작성 페이지<br/>write.html]
|
||||
O --> P[GET /auth/session<br/>작성자·tenant 표시]
|
||||
P --> Q{userUuid와 tenantId 존재?}
|
||||
Q -- 아니오 --> R[제출 차단<br/>다시 로그인 안내]
|
||||
Q -- 예 --> S[카테고리·제목·내용·비밀글 입력]
|
||||
S --> T[등록 버튼 클릭]
|
||||
T --> U[브라우저 UUID 생성<br/>feedbackId]
|
||||
U --> V{첨부파일 존재?}
|
||||
V -- 아니오 --> Y[첨부 메타데이터 빈 배열]
|
||||
V -- 예 --> W[POST presign 요청]
|
||||
W --> X[qa_cdn presigned URL로<br/>파일 PUT 업로드]
|
||||
X --> Y[첨부파일 메타데이터 확정]
|
||||
Y --> Z[feedback envelope 생성]
|
||||
Z --> AA{apiBaseUrl 설정 여부}
|
||||
AA -- 비어 있음<br/>현재 테스트 모드 --> AB[localStorage에 임시 저장]
|
||||
AA -- 설정됨<br/>실서비스 모드 --> AC[POST /v1/qa/feedbacks<br/>feedback.hmac.kr API]
|
||||
AB --> AD[등록 완료 toast<br/>상세 페이지 이동]
|
||||
AC --> AE{API 성공?}
|
||||
AE -- 예 --> AD
|
||||
AE -- 아니오 --> AF[오류 표시<br/>재시도 가능]
|
||||
A[Q&A 주소 진입] --> B{Q&A 세션 유효?}
|
||||
B -->|아니오| C[기존 OIDC SSO 로그인]
|
||||
C --> D[callback 검증·세션 발급]
|
||||
D --> A
|
||||
B -->|예| E[목록 + URL에 지정한 모달]
|
||||
E --> F[GET /auth/session]
|
||||
F --> G[작성자·회사·부서 표시]
|
||||
G --> H[작성 모달 입력·검증]
|
||||
H --> I{첨부파일 있음?}
|
||||
I -->|아니오| J[POST /api/feedbacks JSON]
|
||||
I -->|예| K[POST /api/feedbacks multipart images]
|
||||
J --> L[기존 Worker: 세션·조직 확인 후 ABC 전달]
|
||||
K --> L
|
||||
L --> M{저장 성공?}
|
||||
M -->|예| N[목록 갱신·해당 글 상세 모달]
|
||||
M -->|아니오| O[모달 오류 표시·입력과 파일 유지]
|
||||
```
|
||||
|
||||
## 2. 상태 전이표
|
||||
## 작성·첨부 저장
|
||||
|
||||
| 상태 | 화면/주소 | 진입 조건 | 주요 처리 | 다음 상태 |
|
||||
|---|---|---|---|---|
|
||||
| `S0` | 홈페이지 | Q&A 메뉴 클릭 | 외부 Q&A URL로 이동 | `S1` |
|
||||
| `S1` | `/egbim/` | Q&A 세션 유효 | R2의 목록 HTML 제공 | `S5` |
|
||||
| `S2` | `/auth/login` | 세션 없음 | PKCE 상태 생성, SSO로 redirect | `S3` |
|
||||
| `S3` | `sso.hmac.kr/oidc` | SSO 세션 없음 | 최초 로그인 및 동의 | `S4` |
|
||||
| `S4` | `/auth/callback` | `code`, `state` 수신 | state/PKCE 검증, token/userinfo 조회 | `S5` |
|
||||
| `S5` | 목록 또는 작성 페이지 | `baron_qa_session_v2` 유효 | `/auth/session`에서 사용자 확인 | `S6` |
|
||||
| `S6` | `write.html` | `userUuid`, `tenantId` 확인 | 작성 폼 입력 대기 | `S7` |
|
||||
| `S7` | 작성 페이지 | 등록 클릭 | `feedbackId` UUID 생성 및 입력 검증 | `S8` |
|
||||
| `S8` | 작성 페이지 | 첨부 있음 | presign → `qa_cdn` 업로드 | `S9` |
|
||||
| `S9` | 작성 페이지 | payload 준비 | feedback/support ticket envelope 생성 | `S10` |
|
||||
| `S10` | API 또는 localStorage | `apiBaseUrl` 상태에 따라 분기 | 실제 DB 저장 또는 테스트 저장 | `S11` |
|
||||
| `S11` | `detail.html?id=...` | 저장 성공 | 등록 완료 및 상세 이동 | 종료 |
|
||||
브라우저는 기존 UUID v7 생성 방식으로 `feedbackId`를 만들고 아래 필드를 보낸다. 회사·부서·작성자·이메일 칸은 읽기 전용이며, 서버가 기존 세션·조직 조회 결과에서 작성자 정보를 결정한다.
|
||||
|
||||
## 3. 화면별 사용자 경험
|
||||
|
||||
### A. 홈페이지
|
||||
|
||||
- EG-BIM 메뉴의 Q&A 링크만 제공한다.
|
||||
- 로그인 정보, 쿠키, token, `SESSION_SECRET`을 Q&A로 전달하지 않는다.
|
||||
- 링크 대상:
|
||||
|
||||
```text
|
||||
https://qa-test.baroncs.co.kr/egbim/
|
||||
```json
|
||||
{
|
||||
"feedbackId": "<UUID v7>",
|
||||
"title": "문의 제목",
|
||||
"contents": "문의 내용",
|
||||
"Category": "GENERAL_QNA",
|
||||
"is_secret": "false"
|
||||
}
|
||||
```
|
||||
|
||||
### B. 중앙 SSO
|
||||
- 첨부가 없으면 JSON으로 전송한다.
|
||||
- 첨부가 있으면 같은 필드와 반복 `images` 파일 필드를 `FormData`에 넣는다. `Content-Type`의 boundary는 브라우저가 지정한다.
|
||||
- 기존 Worker가 세션과 첨부를 검증하고 API Key 및 서버 작성자 정보를 붙여 ABC에 전달한다. 파일당 제한은 30MB다.
|
||||
- 브라우저가 presign API나 R2 PUT을 직접 호출하지 않는다. API base가 빈 문자열이면 현재 호스트의 Worker를 사용한다.
|
||||
- API 성공 후 기존 localStorage 캐시를 갱신하고 상세 모달을 연다. 실패를 로컬 저장 성공으로 처리하지 않는다.
|
||||
- 등록 중에는 중복 클릭과 모달 닫기를 막고, 실패하면 입력·파일을 유지하며 재시도할 수 있게 한다.
|
||||
|
||||
- Q&A RP는 `https://sso.hmac.kr/oidc`를 사용한다.
|
||||
- 최초 사용자는 SSO 로그인 및 동의 화면을 볼 수 있다.
|
||||
- 같은 중앙 SSO 세션이 있으면 다음 접근부터 로그인 화면 없이 authorization code가 발급된다.
|
||||
- Q&A RP는 PKCE Public Client이므로 client secret을 보내지 않는다.
|
||||
## 상세·수정·댓글
|
||||
|
||||
### C. Q&A 목록
|
||||
| 동작 | 기존 Worker 요청 |
|
||||
| --- | --- |
|
||||
| 목록 | `GET /api/feedbacks?limit=100&page=...` |
|
||||
| 상세 | `GET /api/feedbacks/{id}` |
|
||||
| 수정 | `PATCH /api/feedbacks/{id}`: `title`, `contents`, `Category`, `is_secret` |
|
||||
| 삭제 | `DELETE /api/feedbacks/{id}` |
|
||||
| 댓글 조회·등록 | `GET/POST /api/feedbacks/{id}/comments` |
|
||||
| 댓글 수정·삭제 | `PATCH/DELETE /api/feedbacks/{id}/comments/{commentId}` |
|
||||
| 완료 확인 | `POST /api/feedbacks/{id}/completion-confirmation` |
|
||||
|
||||
- Worker가 `baron_qa_session_v2`를 검사한다.
|
||||
- 세션이 없으면 `/auth/login?return_url=...`로 이동한다.
|
||||
- 세션이 있으면 R2의 `egbim/index.html`을 제공한다.
|
||||
댓글 텍스트는 JSON `content`, 이미지 첨부 시에는 multipart `content`와 반복 `attachments` 필드를 사용한다. 기존 다중 이미지·형식·30MB 제한과 미리보기를 유지한다. 폐기된 미리보기와 모달을 닫을 때 임시 Blob URL을 해제한다.
|
||||
|
||||
### D. Q&A 작성
|
||||
수정·삭제 버튼은 검증된 기본 requester UUID가 해당 게시글·댓글의 작성자 ID와 같을 때만 표시한다. 이름·이메일 일치로 권한을 부여하지 않는다. 로그인 확인 중·세션 만료·확인 실패·작성자 ID 없음 상태에서는 숨긴다. 게시글과 댓글의 소유자는 각각 판단한다.
|
||||
|
||||
- 브라우저가 `GET /auth/session`을 호출한다.
|
||||
- 다음 작성자 정보를 화면과 payload에 사용한다.
|
||||
수정·완료 확인은 목록에도 반영하고, 삭제 후에는 목록으로 돌아간다. 늦게 도착한 이전 상세 응답은 닫힌 모달의 분리된 DOM에서 처리해 현재 모달을 덮지 않는다. 오류는 모달 안에 표시한다.
|
||||
|
||||
```text
|
||||
userUuid
|
||||
ssoSubject / requesterId
|
||||
tenantId / requesterTenantId
|
||||
tenantIds
|
||||
scope
|
||||
roles
|
||||
email
|
||||
name
|
||||
department
|
||||
phone
|
||||
```
|
||||
## 확인과 배포
|
||||
|
||||
- `requesterId` 또는 `requesterTenantId`가 없으면 제출을 차단한다.
|
||||
|
||||
### E. 글 저장
|
||||
|
||||
- 브라우저에서 `feedbackId` UUID를 1회 생성한다.
|
||||
- 같은 UUID를 다음 식별자로 재사용한다.
|
||||
|
||||
```text
|
||||
feedback.id
|
||||
feedback.source_record_id
|
||||
support_tickets.idempotency_key
|
||||
```
|
||||
|
||||
- 첨부파일은 `qa_cdn`에 저장하고, DB에는 bucket/key 및 파일 메타데이터를 기록한다.
|
||||
- 실제 운영에서는 feedback API가 서버 측에서 Q&A 세션 또는 SSO token을 검증해야 한다.
|
||||
|
||||
## 4. 현재 테스트 모드와 운영 모드
|
||||
|
||||
현재 `egbim/config.js`는 다음과 같이 `apiBaseUrl`이 비어 있다.
|
||||
|
||||
```js
|
||||
apiBaseUrl: ''
|
||||
```
|
||||
|
||||
따라서 현재 등록 결과는 다음과 같이 동작한다.
|
||||
|
||||
```text
|
||||
글 작성 → UUID 생성 → localStorage 저장 → 상세 페이지 이동
|
||||
```
|
||||
|
||||
운영 API 주소를 설정하면 다음 흐름으로 변경된다.
|
||||
|
||||
```text
|
||||
첨부 presign 요청
|
||||
→ qa_cdn 직접 업로드
|
||||
→ feedback.hmac.kr/v1/qa/feedbacks 호출
|
||||
→ feedbacks/support_tickets 트랜잭션 저장
|
||||
→ 상세 페이지 이동
|
||||
```
|
||||
|
||||
## 5. 주요 실패 전이
|
||||
|
||||
| 실패 지점 | 사용자 화면 | 원인/확인 항목 |
|
||||
|---|---|---|
|
||||
| SSO 로그인 전 | 로그인 화면 | 중앙 SSO 세션 없음 또는 다른 SSO 환경 |
|
||||
| callback | `invalid_oauth_state` | OAuth cookie 만료, 호스트 변경, state 불일치 |
|
||||
| token 교환 | `oauth_token_exchange_failed` | issuer/endpoint/client ID/PKCE 설정 불일치 |
|
||||
| 세션 확인 | `authenticated:false` | `baron_qa_session_v2` 없음·만료·`SESSION_SECRET` 불일치 |
|
||||
| 작성자 확인 | UUID/tenant 오류 | SSO scope 또는 claim mapping 누락 |
|
||||
| 첨부 업로드 | 업로드 실패 | presign API 또는 `qa_cdn` 권한 오류 |
|
||||
| DB 저장 | API 요청 오류 | `apiBaseUrl`, CORS, API 인증, DB envelope 규격 오류 |
|
||||
- 기본 검사: `npm run check`, `npm test`.
|
||||
- 별도 브라우저 검사: `npm run test:ui`. Playwright/Chromium 준비와 검증 범위는 [UI 작업 목록](qa-inquiry-ui-redesign-tasks.md#브라우저-회귀-검증)을 참고한다. 모든 요청을 모의 처리하며 실제 서버 데이터를 변경하지 않는다.
|
||||
- 원격 push는 사용자가 진행한다. 스테이징은 기존 main push 자동 배포, 운영은 기존 수동 배포 절차를 사용한다.
|
||||
- 실제 로그인·관리페이지 저장 연동은 스테이징 배포 후 확인한다. 모의 API 검증만으로 실서버 검증 완료로 표시하지 않는다.
|
||||
Reference in new issue
Block a user