디렉토리 구조 개선(egbim 분리)
Deploy EG-BIM QA Gateway / deploy (push) Successful in 40s

This commit is contained in:
root
2026-09-21 16:48:42 +09:00
parent 5cc1504758
commit 9bbd9ed8ce
14 changed files with 28 additions and 29 deletions
+5 -5
View File
@@ -1,6 +1,6 @@
# EG-BIM Q&A static pages # EG-BIM Q&A static pages
`index.html`, `write.html`, `detail.html` 세 페이지로 구성한 EG-BIM Q&A UI입니다. 원본 `egbim_homepage`의 Q&A 화면 구성과 패키지 S/W 메뉴 방향을 참고해, PHP/그누보드/DB 의존성 없이 정적 호스팅에서 동작하도록 분리했습니다. `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`](docs/qa-login-to-save-transition.md)에 정리했습니다. 로그인부터 글 저장까지의 화면·처리 전이맵은 [`docs/qa-login-to-save-transition.md`](docs/qa-login-to-save-transition.md)에 정리했습니다.
@@ -12,7 +12,7 @@
python3 -m http.server 4173 python3 -m http.server 4173
``` ```
그 다음 `http://localhost:4173/`에 접속합니다. 브라우저 보안 정책 때문에 `file://` 직접 열기보다 정적 서버를 사용하는 편이 안전합니다. 그 다음 `http://localhost:4173/egbim/`에 접속합니다. 브라우저 보안 정책 때문에 `file://` 직접 열기보다 정적 서버를 사용하는 편이 안전합니다.
## feedback.hmac.kr DB 기준 연동 ## feedback.hmac.kr DB 기준 연동
@@ -43,7 +43,7 @@ python3 -m http.server 4173
## Cloudflare R2 ## Cloudflare R2
`wrangler.toml``src/index.js`를 추가해 Worker가 R2 정적 파일을 제공하도록 구성했습니다. `/``/index.html` 공개 목록, `/egbim/``/write.html`, `/detail.html`은 로그인 보호 경로입니다. 업로드 스크립트는 루트와 `/egbim/` 경로에 현재 EG-BIM 파일을 함께 올려, 기존 링크와 향후 제품별 prefix 확장을 모두 지원합니다. `wrangler.toml``src/index.js`를 추가해 Worker가 R2 정적 파일을 제공하도록 구성했습니다. 루트(`/`)`/index.html``/egbim/`으로 이동하고, `/egbim/` 아래의 EG-BIM Q&A 페이지는 로그인 보호 경로입니다. EG-BIM 전용 페이지 파일은 `egbim/`에 두고, 스타일과 폰트는 여러 S/W가 공유할 수 있도록 `assets/`에 유지합니다.
```bash ```bash
npm install npm install
@@ -52,7 +52,7 @@ npm run r2:upload
npm run deploy npm run deploy
``` ```
`wrangler deploy`는 Worker와 R2 binding을 배포하고, `npm run r2:upload`정적 파일을 R2 bucket에 올립니다. `npx wrangler r2 object put`은 각 파일을 개별 업로드하므로 업로드 결과를 확인하기 쉽습니다. `wrangler deploy`는 Worker와 R2 binding을 배포하고, `npm run r2:upload``egbim/` 페이지와 공통 `assets/` R2 bucket에 올립니다. `npx wrangler r2 object put`은 각 파일을 개별 업로드하므로 업로드 결과를 확인하기 쉽습니다.
## Wrangler secret 등록 ## Wrangler secret 등록
@@ -64,7 +64,7 @@ openssl rand -hex 32
npx wrangler secret bulk qa-secrets.json --name baron-qa-gateway-test npx wrangler secret bulk qa-secrets.json --name baron-qa-gateway-test
``` ```
`ABC_API_KEY`에는 프로젝트 전용 ABC API Key를 등록합니다. API Key는 작성페이지 코드나 `assets/config.js`에 넣지 않습니다. `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://app.brsw.kr/oidc`를 사용합니다. 이 RP는 PKCE Public Client이므로 `AUTH_CLIENT_SECRET`은 사용하지 않으며, `SESSION_SECRET`만 secret으로 등록합니다. `AUTH_CLIENT_ID`, `AUTH_AUTHORIZE_URL`, `AUTH_TOKEN_URL`, 선택적인 `AUTH_USERINFO_URL``wrangler.toml`에 실제 SSO 값으로 설정해야 합니다. 현재 Q&A RP는 `https://app.brsw.kr/oidc`를 사용합니다. 이 RP는 PKCE Public Client이므로 `AUTH_CLIENT_SECRET`은 사용하지 않으며, `SESSION_SECRET`만 secret으로 등록합니다.
+1 -1
View File
@@ -67,7 +67,7 @@
## 보안 요구사항 ## 보안 요구사항
- [x] API Key를 정적 JavaScript, HTML, `assets/config.js`에 넣지 않음 - [x] API Key를 정적 JavaScript, HTML, `egbim/config.js`에 넣지 않음
- [x] API Key는 Cloudflare Worker Secret에만 저장 - [x] API Key는 Cloudflare Worker Secret에만 저장
- [x] requester 정보는 브라우저 입력값이 아닌 검증된 SSO 세션에서 생성 - [x] requester 정보는 브라우저 입력값이 아닌 검증된 SSO 세션에서 생성
- [x] 전화번호는 화면 입력값을 받지 않고 SSO 프로필에서만 읽음 - [x] 전화번호는 화면 입력값을 받지 않고 SSO 프로필에서만 읽음
+1 -2
View File
@@ -130,7 +130,7 @@ support_tickets.idempotency_key
## 4. 현재 테스트 모드와 운영 모드 ## 4. 현재 테스트 모드와 운영 모드
현재 `assets/config.js`는 다음과 같이 `apiBaseUrl`이 비어 있다. 현재 `egbim/config.js`는 다음과 같이 `apiBaseUrl`이 비어 있다.
```js ```js
apiBaseUrl: '' apiBaseUrl: ''
@@ -163,4 +163,3 @@ apiBaseUrl: ''
| 작성자 확인 | UUID/tenant 오류 | SSO scope 또는 claim mapping 누락 | | 작성자 확인 | UUID/tenant 오류 | SSO scope 또는 claim mapping 누락 |
| 첨부 업로드 | 업로드 실패 | presign API 또는 `qa_cdn` 권한 오류 | | 첨부 업로드 | 업로드 실패 | presign API 또는 `qa_cdn` 권한 오류 |
| DB 저장 | API 요청 오류 | `apiBaseUrl`, CORS, API 인증, DB envelope 규격 오류 | | DB 저장 | API 요청 오류 | `apiBaseUrl`, CORS, API 인증, DB envelope 규격 오류 |
@@ -130,7 +130,7 @@ support_tickets.idempotency_key
## 4. 현재 테스트 모드와 운영 모드 ## 4. 현재 테스트 모드와 운영 모드
현재 `assets/config.js`는 다음과 같이 `apiBaseUrl`이 비어 있다. 현재 `egbim/config.js`는 다음과 같이 `apiBaseUrl`이 비어 있다.
```js ```js
apiBaseUrl: '' apiBaseUrl: ''
@@ -185,7 +185,7 @@ EG-BIM을 사용하면서 문제가 생겼거나 지원이 필요한 사용자
## 10. 현재 테스트 모드에서의 범위 ## 10. 현재 테스트 모드에서의 범위
현재 `assets/config.js``apiBaseUrl`이 비어 있으면 문의 등록 결과는 브라우저 `localStorage`에 임시 저장된다. 따라서 사용자는 등록 완료와 상세 이동을 경험할 수 있지만, 다음 백스테이지 여정은 실제로 실행되지 않는다. 현재 `egbim/config.js``apiBaseUrl`이 비어 있으면 문의 등록 결과는 브라우저 `localStorage`에 임시 저장된다. 따라서 사용자는 등록 완료와 상세 이동을 경험할 수 있지만, 다음 백스테이지 여정은 실제로 실행되지 않는다.
- ABC DB 저장 - ABC DB 저장
- 관리페이지 목록 반영 - 관리페이지 목록 반영
@@ -195,4 +195,3 @@ EG-BIM을 사용하면서 문제가 생겼거나 지원이 필요한 사용자
- 작성자 완료 확인의 서버 검증 - 작성자 완료 확인의 서버 검증
운영 여정을 검증하려면 실제 API 연결, Q&A 세션 검증, 알림 채널 설정, 수신자 매핑, 공개 댓글·내부 메모 권한 검증을 함께 완료해야 한다. 운영 여정을 검증하려면 실제 API 연결, Q&A 세션 검증, 알림 채널 설정, 수신자 매핑, 공개 댓글·내부 메모 권한 검증을 함께 완료해야 한다.
+1
View File
@@ -1,3 +1,4 @@
/* EG-BIM-specific Q&A page behavior. */
(function () { (function () {
'use strict'; 'use strict';
+1 -1
View File
@@ -1,4 +1,4 @@
/* Static deployment configuration. Keep credentials and R2 secrets out of this file. */ /* EG-BIM page configuration. Keep credentials and R2 secrets out of this file. */
window.QA_CONFIG = Object.assign({ window.QA_CONFIG = Object.assign({
product: 'egbim', product: 'egbim',
apiBaseUrl: '', apiBaseUrl: '',
+2 -2
View File
@@ -1,8 +1,8 @@
<!doctype html> <!doctype html>
<html lang="ko"> <html lang="ko">
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>문의 상세 | EG-BIM Q&amp;A</title><link rel="stylesheet" href="assets/styles.css"></head> <head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>문의 상세 | EG-BIM Q&amp;A</title><link rel="stylesheet" href="../assets/styles.css"></head>
<body> <body>
<main><section class="page-hero"><div class="hero-inner"><span class="hero-kicker">EG-BIM SUPPORT</span><h1>Q&amp;A</h1><p>EG-BIM 관련 문의하기</p></div></section> <main><section class="page-hero"><div class="hero-inner"><span class="hero-kicker">EG-BIM SUPPORT</span><h1>Q&amp;A</h1><p>EG-BIM 관련 문의하기</p></div></section>
<section class="page-shell" id="detail"><div class="section-heading"><div><h2>문의 상세</h2><p>문의 내용과 답변을 확인할 수 있습니다.</p></div><a class="button button-primary" href="write.html">문의 등록</a></div><article class="detail-card"><div class="detail-head"><div class="detail-title-line"><h2><span class="category" data-detail-category>문의</span> <span data-detail-title>문의 제목</span></h2><span class="status review" data-detail-status>문의접수</span></div><div class="detail-meta"><span data-detail-date>2026-09-17</span><span data-detail-author>작성자</span><span data-detail-company>회사</span><span data-detail-department>부서</span><span data-detail-secret hidden>🔒 비밀글</span></div></div><div class="detail-body" data-detail-content></div><div class="attachments"><h3>첨부파일</h3><span class="help-text">첨부된 파일이 없습니다.</span></div><div class="detail-actions"><a class="button button-outline" href="index.html">목록</a><a class="button button-outline" href="write.html">문의 등록</a></div></article><section class="comment-box"><h3>답변 <span id="commentCount">0</span></h3><div id="comments"></div></section><div class="form-footer"><button class="button button-outline" id="backButton" type="button">목록으로</button></div></section> <section class="page-shell" id="detail"><div class="section-heading"><div><h2>문의 상세</h2><p>문의 내용과 답변을 확인할 수 있습니다.</p></div><a class="button button-primary" href="write.html">문의 등록</a></div><article class="detail-card"><div class="detail-head"><div class="detail-title-line"><h2><span class="category" data-detail-category>문의</span> <span data-detail-title>문의 제목</span></h2><span class="status review" data-detail-status>문의접수</span></div><div class="detail-meta"><span data-detail-date>2026-09-17</span><span data-detail-author>작성자</span><span data-detail-company>회사</span><span data-detail-department>부서</span><span data-detail-secret hidden>🔒 비밀글</span></div></div><div class="detail-body" data-detail-content></div><div class="attachments"><h3>첨부파일</h3><span class="help-text">첨부된 파일이 없습니다.</span></div><div class="detail-actions"><a class="button button-outline" href="index.html">목록</a><a class="button button-outline" href="write.html">문의 등록</a></div></article><section class="comment-box"><h3>답변 <span id="commentCount">0</span></h3><div id="comments"></div></section><div class="form-footer"><button class="button button-outline" id="backButton" type="button">목록으로</button></div></section>
</main><script src="assets/config.js?v=20260921-3"></script><script src="app.js?v=20260921-3"></script> </main><script src="config.js?v=20260921-3"></script><script src="app.js?v=20260921-3"></script>
</body></html> </body></html>
+2 -2
View File
@@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="EG-BIM Q&A 문의 게시판"> <meta name="description" content="EG-BIM Q&A 문의 게시판">
<title>EG-BIM Q&A | 바론컨설턴트</title> <title>EG-BIM Q&A | 바론컨설턴트</title>
<link rel="stylesheet" href="assets/styles.css"> <link rel="stylesheet" href="../assets/styles.css">
</head> </head>
<body> <body>
<main> <main>
@@ -22,6 +22,6 @@
</section> </section>
</main> </main>
<div id="toast" class="toast" role="status" aria-live="polite"></div> <div id="toast" class="toast" role="status" aria-live="polite"></div>
<script src="assets/config.js?v=20260921-3"></script><script src="app.js?v=20260921-3"></script> <script src="config.js?v=20260921-3"></script><script src="app.js?v=20260921-3"></script>
</body> </body>
</html> </html>
+2 -2
View File
@@ -1,8 +1,8 @@
<!doctype html> <!doctype html>
<html lang="ko"> <html lang="ko">
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>문의 등록 | EG-BIM Q&amp;A</title><link rel="stylesheet" href="assets/styles.css"></head> <head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>문의 등록 | EG-BIM Q&amp;A</title><link rel="stylesheet" href="../assets/styles.css"></head>
<body> <body>
<main><section class="page-hero"><div class="hero-inner"><span class="hero-kicker">EG-BIM SUPPORT</span><h1>Q&amp;A</h1><p>EG-BIM 관련 문의하기</p></div></section> <main><section class="page-hero"><div class="hero-inner"><span class="hero-kicker">EG-BIM SUPPORT</span><h1>Q&amp;A</h1><p>EG-BIM 관련 문의하기</p></div></section>
<section class="page-shell"><div class="section-heading"><div><h2>문의 등록</h2><p>로그인한 사용자 정보로 문의가 등록됩니다.</p></div></div><div class="form-notice" data-auth-state>로그인 상태를 확인하고 있습니다.</div><div class="form-error" id="formError" role="alert"></div><form id="qaForm" novalidate><div class="form-card" id="writeFields"><div class="form-row"><label class="form-label" for="category">구분 <span class="required">*</span></label><div class="form-control"><select class="select" id="category" required><option value="">선택해주세요</option><option>오류문의</option><option>개선문의</option><option>일반문의</option></select></div></div><div class="form-row"><label class="form-label" for="title">제목 <span class="required">*</span></label><div class="form-control"><input class="input" id="title" type="text" maxlength="100" placeholder="문의 제목을 입력해주세요" required></div></div><div class="form-row"><span class="form-label">공개 설정</span><div class="form-control"><label class="check"><input id="secret" type="checkbox"> 비밀글로 등록</label></div></div><div class="form-row"><label class="form-label" for="content">내용 <span class="required">*</span></label><div class="form-control column"><textarea class="textarea" id="content" placeholder="문의 내용을 작성해주세요" required></textarea><span class="help-text">개인정보, 비밀번호, 인증번호 등 민감한 정보는 입력하지 마세요.</span></div></div><div class="form-row"><label class="form-label" for="attachment">첨부파일</label><div class="form-control column"><input class="file-input" id="attachment" type="file" multiple><span class="help-text">파일은 presigned URL을 통해 qa_cdn 버킷에 저장됩니다. 파일당 최대 30MB</span></div></div></div><div class="form-footer"><a class="button button-outline" href="index.html">취소</a><button class="button button-primary" id="submitButton" type="submit">문의 등록</button></div></form></section> <section class="page-shell"><div class="section-heading"><div><h2>문의 등록</h2><p>로그인한 사용자 정보로 문의가 등록됩니다.</p></div></div><div class="form-notice" data-auth-state>로그인 상태를 확인하고 있습니다.</div><div class="form-error" id="formError" role="alert"></div><form id="qaForm" novalidate><div class="form-card" id="writeFields"><div class="form-row"><label class="form-label" for="category">구분 <span class="required">*</span></label><div class="form-control"><select class="select" id="category" required><option value="">선택해주세요</option><option>오류문의</option><option>개선문의</option><option>일반문의</option></select></div></div><div class="form-row"><label class="form-label" for="title">제목 <span class="required">*</span></label><div class="form-control"><input class="input" id="title" type="text" maxlength="100" placeholder="문의 제목을 입력해주세요" required></div></div><div class="form-row"><span class="form-label">공개 설정</span><div class="form-control"><label class="check"><input id="secret" type="checkbox"> 비밀글로 등록</label></div></div><div class="form-row"><label class="form-label" for="content">내용 <span class="required">*</span></label><div class="form-control column"><textarea class="textarea" id="content" placeholder="문의 내용을 작성해주세요" required></textarea><span class="help-text">개인정보, 비밀번호, 인증번호 등 민감한 정보는 입력하지 마세요.</span></div></div><div class="form-row"><label class="form-label" for="attachment">첨부파일</label><div class="form-control column"><input class="file-input" id="attachment" type="file" multiple><span class="help-text">파일은 presigned URL을 통해 qa_cdn 버킷에 저장됩니다. 파일당 최대 30MB</span></div></div></div><div class="form-footer"><a class="button button-outline" href="index.html">취소</a><button class="button button-primary" id="submitButton" type="submit">문의 등록</button></div></form></section>
</main><div id="toast" class="toast" role="status" aria-live="polite"></div><script src="assets/config.js?v=20260921-3"></script><script src="app.js?v=20260921-3"></script> </main><div id="toast" class="toast" role="status" aria-live="polite"></div><script src="config.js?v=20260921-3"></script><script src="app.js?v=20260921-3"></script>
</body></html> </body></html>
+1 -1
View File
@@ -5,7 +5,7 @@
"scripts": { "scripts": {
"deploy": "wrangler deploy", "deploy": "wrangler deploy",
"r2:upload": "bash scripts/upload-r2.sh", "r2:upload": "bash scripts/upload-r2.sh",
"check": "node --check src/index.js && node --check app.js" "check": "node --check src/index.js && node --check egbim/app.js"
}, },
"devDependencies": { "devDependencies": {
"wrangler": "4.134.0" "wrangler": "4.134.0"
+5 -9
View File
@@ -10,14 +10,11 @@ upload() {
npx wrangler r2 object put "$bucket/$key" --file "$file" --content-type "$content_type" --remote npx wrangler r2 object put "$bucket/$key" --file "$file" --content-type "$content_type" --remote
} }
upload index.html index.html text/html upload egbim/index.html egbim/index.html text/html
upload write.html write.html text/html upload egbim/write.html egbim/write.html text/html
upload detail.html detail.html text/html upload egbim/detail.html egbim/detail.html text/html
upload app.js app.js application/javascript upload egbim/app.js egbim/app.js application/javascript
upload index.html egbim/index.html text/html upload egbim/config.js egbim/config.js application/javascript
upload write.html egbim/write.html text/html
upload detail.html egbim/detail.html text/html
upload app.js egbim/app.js application/javascript
while IFS= read -r file; do while IFS= read -r file; do
key="${file#./}" key="${file#./}"
@@ -31,7 +28,6 @@ while IFS= read -r file; do
*) content_type="application/octet-stream" ;; *) content_type="application/octet-stream" ;;
esac esac
upload "$file" "$key" "$content_type" upload "$file" "$key" "$content_type"
upload "$file" "egbim/$key" "$content_type"
done < <(find ./assets -type f -not -name '*.map' -print | sort) done < <(find ./assets -type f -not -name '*.map' -print | sort)
echo "Uploaded static files to R2 bucket: $bucket" echo "Uploaded static files to R2 bucket: $bucket"
+4
View File
@@ -16,6 +16,10 @@ export default {
return json({ error: 'method_not_allowed' }, 405); return json({ error: 'method_not_allowed' }, 405);
} }
if (url.pathname === '/' || url.pathname === '/index.html') {
return Response.redirect(new URL('/egbim/', url), 301);
}
if (matchesConfiguredPath(url.pathname, env.INTERNAL_ONLY_PREFIXES) && !isInternalRequest(request, env)) { if (matchesConfiguredPath(url.pathname, env.INTERNAL_ONLY_PREFIXES) && !isInternalRequest(request, env)) {
return json({ error: 'not_found' }, 404); return json({ error: 'not_found' }, 404);
} }
+1 -1
View File
@@ -28,7 +28,7 @@ ABC_IDEMPOTENCY_CONSUMER = "EGBIM_QA"
PUBLIC_EXACT_PATHS = "/,/index.html,/favicon.ico" PUBLIC_EXACT_PATHS = "/,/index.html,/favicon.ico"
PUBLIC_PREFIXES = "/egbim/,/tova/,/gaia/,/shared/,/assets/,/auth/" PUBLIC_PREFIXES = "/egbim/,/tova/,/gaia/,/shared/,/assets/,/auth/"
PROTECTED_PREFIXES = "/egbim/,/tova/,/gaia/" PROTECTED_PREFIXES = "/egbim/,/tova/,/gaia/"
PROTECTED_EXACT_PATHS = "/write.html,/detail.html" PROTECTED_EXACT_PATHS = "/egbim/write.html,/egbim/detail.html"
INTERNAL_ONLY_PREFIXES = "/protected/" INTERNAL_ONLY_PREFIXES = "/protected/"
[[r2_buckets]] [[r2_buckets]]