EG-BIM Q&A static pages

egbim/ 아래의 index.html, write.html, detail.html 세 페이지로 구성한 EG-BIM Q&A UI입니다. 원본 egbim_homepage의 Q&A 화면 구성과 패키지 S/W 메뉴 방향을 참고해, PHP/그누보드/DB 의존성 없이 정적 호스팅에서 동작하도록 분리했습니다.

로그인부터 글 저장까지의 화면·처리 전이맵은 docs/qa-login-to-save-transition.md에 정리했습니다.

로컬 확인

정적 파일 서버에서 루트를 열면 됩니다.

python3 -m http.server 4173

그 다음 http://localhost:4173/egbim/에 접속합니다. 브라우저 보안 정책 때문에 file:// 직접 열기보다 정적 서버를 사용하는 편이 안전합니다.

feedback.hmac.kr DB 기준 연동

덤프의 EG-BIM 운영 대상은 다음 값으로 고정했습니다.

  • workspaces.id = 6, workspace_code = EGBIM
  • channels.name = Q&A, channels.id = 01a0ae40-51c2-7647-a93c-0249b3759777
  • 카테고리: ERROR_QNA, IMPROVEMENT_QNA, GENERAL_QNA
  • 신규 상태: support_tickets.status_code = RECEIVED, feedback_status = NEW
  • 첨부 저장소: storage_bucket = qa_cdn

작성 시 브라우저가 UUID를 하나 생성합니다. 같은 UUID를 feedback.id, feedback.source_record_id, support_tickets.idempotency_key에 사용하므로 중복 제출을 판별할 수 있습니다. API 서버는 envelope를 받아 다음 DB 레코드를 하나의 트랜잭션으로 생성해야 합니다.

  • feedbacks: id, channel_id, source_namespace, source_record_id, data
  • support_tickets: 작성자/테넌트/제목/내용/분류/상태/idempotency_key
  • feedback_comment_attachments: 답변 댓글 이미지가 생길 때 comment_id와 qa_cdn 메타데이터
  • support_attachments: 운영 호환 레이어가 필요할 때 동일 파일의 티켓 첨부 메타데이터

상세 페이지에는 공개 댓글 입력 UI가 있으며, 댓글은 같은 Worker를 통해 ABC의 feedback_comments에 저장하고 is_internal=false인 공개 댓글만 조회합니다. 댓글 이미지가 있으면 attachments multipart 필드로 Worker가 ABC에 전달하며, 응답으로 받은 이미지 URL을 썸네일로 표시합니다. 목록·상세 진입 시에는 ABC의 feedback_status를 다시 조회해 관리페이지에서 변경된 상태를 반영합니다.

연동 지점

  • SSO: Q&A는 홈페이지 로그인 정보를 전달받지 않고, https://sso.hmac.kr/oidc에 등록된 독립적인 PKCE Public Client로 직접 인증합니다. callback은 https://qa-test.baroncs.co.kr/auth/callback이며, Worker가 검증한 claim을 버전이 지정된 HttpOnly 세션 쿠키에 저장합니다. 브라우저의 GET /auth/session은 Q&A 세션에 저장된 작성자 정보를 반환합니다. feedback 서버는 반드시 SSO 세션 또는 토큰을 서버 측에서 검증해야 합니다.
  • 작성자 식별자: ssoSubject/requesterId, userUuid, tenantId/requesterTenantId, tenantIds, scope, roles, 이메일·이름·부서·전화번호를 payload에 넣습니다. requester_id와 requester_tenant_id가 없으면 제출을 차단합니다.
  • API: 작성페이지는 같은 Worker의 POST /api/feedbacks를 호출합니다. Worker가 Q&A 세션 쿠키를 검증하고, SSO requester 정보와 ABC_API_KEY Secret을 추가한 뒤 POST https://feedback.hmac.kr/api/projects/{projectId}/channels/{channelId}/feedbacks로 전달합니다. API Key와 내부 ABC 주소는 브라우저에 노출하지 않습니다.
  • 첨부파일: 작성페이지가 multipart/form-data의 반복 images 필드로 파일을 Worker에 전달하고, Worker가 검증된 requester 정보와 API Key를 추가해 ABC API로 전달합니다. ABC API가 qa_cdn 저장소에 파일을 저장하므로 R2 access key/secret은 정적 페이지에 넣지 않습니다.
  • 텍스트만 등록할 때는 기존 JSON 요청을 사용하고, 파일이 있을 때만 multipart 요청을 사용합니다.

Cloudflare R2

wrangler.toml과 src/index.js를 추가해 Worker가 R2 정적 파일을 제공하도록 구성했습니다. 루트(/)와 /index.html은 /egbim/으로 이동하고, /egbim/ 아래의 EG-BIM Q&A 페이지는 로그인 보호 경로입니다. EG-BIM 전용 페이지 파일은 egbim/에 두고, 스타일과 폰트는 여러 S/W가 공유할 수 있도록 assets/에 유지합니다.

npm install
npm run check
npm run r2:upload
npm run deploy

wrangler deploy는 Worker와 R2 binding을 배포하고, npm run r2:upload는 egbim/ 페이지와 공통 assets/를 R2 bucket에 올립니다. npx wrangler r2 object put은 각 파일을 개별 업로드하므로 업로드 결과를 확인하기 쉽습니다.

Wrangler secret 등록

실제 secret은 저장소에 만들지 않습니다. qa-secrets.json.example을 복사해 qa-secrets.json을 만들고 값을 채운 뒤 등록합니다.

cp qa-secrets.json.example qa-secrets.json
openssl rand -hex 32
npx wrangler secret bulk qa-secrets.json --name baron-qa-gateway-test

ABC_API_KEY에는 프로젝트 전용 ABC API Key를 등록합니다. API Key는 작성페이지 코드나 egbim/config.js에 넣지 않습니다.

AUTH_CLIENT_ID, AUTH_AUTHORIZE_URL, AUTH_TOKEN_URL, 선택적인 AUTH_USERINFO_URL은 wrangler.toml에 실제 SSO 값으로 설정해야 합니다. 현재 Q&A RP는 https://sso.hmac.kr/oidc를 사용합니다. 이 RP는 PKCE Public Client이므로 AUTH_CLIENT_SECRET은 사용하지 않으며, SESSION_SECRET만 secret으로 등록합니다.

Worker는 OAuth Authorization Code + PKCE를 사용하고, callback에서 검증한 사용자 claim을 서명된 HttpOnly 세션 쿠키에 저장합니다. 브라우저의 GET /auth/session은 정규화된 작성자 정보만 반환합니다.

Gitea Actions 등록값

운영 qa.baroncs.co.kr 배포는 production 파이프라인 안내를 따른다. 운영 workflow는 Deploy EG-BIM QA Production이며 main에서 수동으로 실행한다. 아래 기존 등록값은 스테이징 배포용이다.

저장소 Settings → Actions → Secrets에 아래 기본 Secret 3개를 등록합니다.

이름 종류 값
CLOUDFLARE_API_TOKEN Secret Workers Scripts Edit + Workers R2 Storage Edit 권한의 Cloudflare API Token
SESSION_SECRET Secret openssl rand -hex 32로 생성한 세션 서명키
ABC_API_KEY Secret 프로젝트 전용 ABC UserFeedback API Key

CLOUDFLARE_ACCOUNT_ID는 secret으로 등록할 필요가 없습니다. wrangler.toml에 81fa2d48964d31dd0da9558f9ce601d1로 설정되어 있습니다.

Cloudflare API Token에는 최소한 다음 권한이 필요합니다.

  • Account → Workers Scripts → Edit
  • Account → Workers R2 Storage → Edit
  • Account → Account Settings → Read
  • Custom Domain route를 Actions에서 변경할 경우 Zone → Workers Routes → Edit

.gitea/workflows/deploy.yml은 main push 또는 수동 실행 시 세션 secret을 Worker에 등록하고 R2 업로드 후 baron-qa-gateway-test를 배포합니다. 실제 secret 값은 로그에 출력하지 않습니다.

현재 화면은 API 설정 전에도 QA 흐름을 확인할 수 있도록 샘플 글과 로컬 테스트 저장을 포함합니다. 운영 배포 시에는 Gitea Secret ABC_API_KEY가 Worker에 등록되어야 하며, 작성페이지는 Worker 프록시를 통해 ABC API를 호출합니다.

회사·부서 조직 연동 (스테이징)

Gitea의 스테이징 설정을 .gitea/workflows/deploy.yml에서 Worker 런타임 이름으로 전달합니다.

Gitea 위치 Worker 런타임
STAGING_ORG_CONTEXT_KEY_ID Secrets ORG_CONTEXT_KEY_ID (Secret)
STAGING_ORG_CONTEXT_KEY_SECRET Secrets ORG_CONTEXT_KEY_SECRET (Secret)
STAGING_ORG_CONTEXT_ENABLED Variables ORG_CONTEXT_ENABLED
STAGING_ORG_CONTEXT_API_URL Variables ORG_CONTEXT_API_URL
STAGING_ORG_CONTEXT_SCOPE_MODE Variables ORG_CONTEXT_SCOPE_MODE
STAGING_ORG_CONTEXT_EXPOSURE Variables ORG_CONTEXT_EXPOSURE
STAGING_ORG_CONTEXT_TENANT_IDS Variables ORG_CONTEXT_TENANT_IDS
STAGING_ORG_CONTEXT_CACHE_TTL_SECONDS Variables, 선택 ORG_CONTEXT_CACHE_TTL_SECONDS (기본 300)

조회는 SCOPED, identifiers를 사용합니다. 기준 테넌트는 UUID JSON 배열로 등록하며 UUID v4를 허용합니다. 각 테넌트를 최대 3개씩 동시 조회하고, 완전한 응답만 캐시합니다. 페이지 조회 타임아웃은 요청당 5초입니다. 배포 전 npm run org:verify는 요청당 15초를 허용하고, 타임아웃·통신 실패·HTTP 429/502/503/504에 한해 2초 간격으로 최대 3회 시도합니다. 인증 실패·범위 불일치·구성원 누락은 재시도 없이 검증 실패로 처리합니다.

기본 ORG_CONTEXT_ENABLED=false에서는 조직 API를 호출하지 않습니다. true로 배포하면 npm run org:verify가 실제 키로 조직 API를 먼저 검증하며, 실패 시 새 Worker 배포를 진행하지 않습니다. 성공 시에는 조직·사용자·소속 건수만 로그에 출력합니다. 전체 응답·전화번호·키 원문은 출력하지 않습니다.

Worker는 서명된 세션의 사용자 UUID로 소속을 찾습니다. 조회 조직 컨텍스트·대표 소속을 선택하고, 기존 requester ID·tenant ID·권한은 유지합니다. 새 문의는 requester_affiliation에 회사, requester_department에 부서를 저장합니다. JSON과 첨부 multipart에 같은 값을 사용합니다. 목록·상세는 글에 저장된 정보를 사용하며 조회 시점의 조직으로 과거 글을 바꾸지 않습니다.

조직 조회가 실패하면 기존 세션·글쓰기 흐름과 확인된 SSO 정보를 유지합니다. 누락되거나 모호한 사용자는 orgContextStatus로 구분하고 다른 작성자의 정보를 대신 쓰지 않습니다.

로컬 검증:

npm run check
npm test
WRANGLER_LOG_PATH=/tmp/qa-worker-build.log node scripts/deploy-worker.mjs --dry-run

마지막 명령은 Secret 등록이나 배포를 실행하지 않고 번들만 확인합니다. 실제 키를 로컬로 준비했다면 런타임 환경변수 이름으로 주입한 뒤 npm run org:verify를 실행할 수 있습니다. 키 값을 커맨드라인 인자나 소스에 넣지 않습니다.

기존 마이그레이션 글의 보정은 baron_qa/scripts/qa_org_metadata.py 도구에서 별도로 수행합니다. 개인정보가 포함된 엑셀·보정 보고서를 이 저장소의 정적 파일이나 Worker 번들에 넣지 않습니다.

S
Description
No description provided
Readme
15 MiB
0 Stars 1 Watchers 0 Forks
Languages
HTML 69.1%
JavaScript 17.6%
CSS 13.2%