This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# 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)에 정리했습니다.
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
python3 -m http.server 4173
|
||||
```
|
||||
|
||||
그 다음 `http://localhost:4173/`에 접속합니다. 브라우저 보안 정책 때문에 `file://` 직접 열기보다 정적 서버를 사용하는 편이 안전합니다.
|
||||
그 다음 `http://localhost:4173/egbim/`에 접속합니다. 브라우저 보안 정책 때문에 `file://` 직접 열기보다 정적 서버를 사용하는 편이 안전합니다.
|
||||
|
||||
## feedback.hmac.kr DB 기준 연동
|
||||
|
||||
@@ -43,7 +43,7 @@ python3 -m http.server 4173
|
||||
|
||||
## 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
|
||||
npm install
|
||||
@@ -52,7 +52,7 @@ npm run r2:upload
|
||||
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 등록
|
||||
|
||||
@@ -64,7 +64,7 @@ openssl rand -hex 32
|
||||
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으로 등록합니다.
|
||||
|
||||
|
||||
@@ -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] requester 정보는 브라우저 입력값이 아닌 검증된 SSO 세션에서 생성
|
||||
- [x] 전화번호는 화면 입력값을 받지 않고 SSO 프로필에서만 읽음
|
||||
|
||||
@@ -130,7 +130,7 @@ support_tickets.idempotency_key
|
||||
|
||||
## 4. 현재 테스트 모드와 운영 모드
|
||||
|
||||
현재 `assets/config.js`는 다음과 같이 `apiBaseUrl`이 비어 있다.
|
||||
현재 `egbim/config.js`는 다음과 같이 `apiBaseUrl`이 비어 있다.
|
||||
|
||||
```js
|
||||
apiBaseUrl: ''
|
||||
@@ -163,4 +163,3 @@ apiBaseUrl: ''
|
||||
| 작성자 확인 | UUID/tenant 오류 | SSO scope 또는 claim mapping 누락 |
|
||||
| 첨부 업로드 | 업로드 실패 | presign API 또는 `qa_cdn` 권한 오류 |
|
||||
| DB 저장 | API 요청 오류 | `apiBaseUrl`, CORS, API 인증, DB envelope 규격 오류 |
|
||||
|
||||
|
||||
@@ -130,7 +130,7 @@ support_tickets.idempotency_key
|
||||
|
||||
## 4. 현재 테스트 모드와 운영 모드
|
||||
|
||||
현재 `assets/config.js`는 다음과 같이 `apiBaseUrl`이 비어 있다.
|
||||
현재 `egbim/config.js`는 다음과 같이 `apiBaseUrl`이 비어 있다.
|
||||
|
||||
```js
|
||||
apiBaseUrl: ''
|
||||
|
||||
@@ -185,7 +185,7 @@ EG-BIM을 사용하면서 문제가 생겼거나 지원이 필요한 사용자
|
||||
|
||||
## 10. 현재 테스트 모드에서의 범위
|
||||
|
||||
현재 `assets/config.js`의 `apiBaseUrl`이 비어 있으면 문의 등록 결과는 브라우저 `localStorage`에 임시 저장된다. 따라서 사용자는 등록 완료와 상세 이동을 경험할 수 있지만, 다음 백스테이지 여정은 실제로 실행되지 않는다.
|
||||
현재 `egbim/config.js`의 `apiBaseUrl`이 비어 있으면 문의 등록 결과는 브라우저 `localStorage`에 임시 저장된다. 따라서 사용자는 등록 완료와 상세 이동을 경험할 수 있지만, 다음 백스테이지 여정은 실제로 실행되지 않는다.
|
||||
|
||||
- ABC DB 저장
|
||||
- 관리페이지 목록 반영
|
||||
@@ -195,4 +195,3 @@ EG-BIM을 사용하면서 문제가 생겼거나 지원이 필요한 사용자
|
||||
- 작성자 완료 확인의 서버 검증
|
||||
|
||||
운영 여정을 검증하려면 실제 API 연결, Q&A 세션 검증, 알림 채널 설정, 수신자 매핑, 공개 댓글·내부 메모 권한 검증을 함께 완료해야 한다.
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* EG-BIM-specific Q&A page behavior. */
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
@@ -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({
|
||||
product: 'egbim',
|
||||
apiBaseUrl: '',
|
||||
@@ -1,8 +1,8 @@
|
||||
<!doctype html>
|
||||
<html lang="ko">
|
||||
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>문의 상세 | EG-BIM Q&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&A</title><link rel="stylesheet" href="../assets/styles.css"></head>
|
||||
<body>
|
||||
<main><section class="page-hero"><div class="hero-inner"><span class="hero-kicker">EG-BIM SUPPORT</span><h1>Q&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>
|
||||
</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>
|
||||
@@ -5,7 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="EG-BIM Q&A 문의 게시판">
|
||||
<title>EG-BIM Q&A | 바론컨설턴트</title>
|
||||
<link rel="stylesheet" href="assets/styles.css">
|
||||
<link rel="stylesheet" href="../assets/styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
@@ -22,6 +22,6 @@
|
||||
</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>
|
||||
<script src="config.js?v=20260921-3"></script><script src="app.js?v=20260921-3"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,8 +1,8 @@
|
||||
<!doctype html>
|
||||
<html lang="ko">
|
||||
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>문의 등록 | EG-BIM Q&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&A</title><link rel="stylesheet" href="../assets/styles.css"></head>
|
||||
<body>
|
||||
<main><section class="page-hero"><div class="hero-inner"><span class="hero-kicker">EG-BIM SUPPORT</span><h1>Q&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>
|
||||
</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>
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
"scripts": {
|
||||
"deploy": "wrangler deploy",
|
||||
"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": {
|
||||
"wrangler": "4.134.0"
|
||||
|
||||
@@ -10,14 +10,11 @@ upload() {
|
||||
npx wrangler r2 object put "$bucket/$key" --file "$file" --content-type "$content_type" --remote
|
||||
}
|
||||
|
||||
upload index.html index.html text/html
|
||||
upload write.html write.html text/html
|
||||
upload detail.html detail.html text/html
|
||||
upload app.js app.js application/javascript
|
||||
upload index.html egbim/index.html text/html
|
||||
upload write.html egbim/write.html text/html
|
||||
upload detail.html egbim/detail.html text/html
|
||||
upload app.js egbim/app.js application/javascript
|
||||
upload egbim/index.html egbim/index.html text/html
|
||||
upload egbim/write.html egbim/write.html text/html
|
||||
upload egbim/detail.html egbim/detail.html text/html
|
||||
upload egbim/app.js egbim/app.js application/javascript
|
||||
upload egbim/config.js egbim/config.js application/javascript
|
||||
|
||||
while IFS= read -r file; do
|
||||
key="${file#./}"
|
||||
@@ -31,7 +28,6 @@ while IFS= read -r file; do
|
||||
*) content_type="application/octet-stream" ;;
|
||||
esac
|
||||
upload "$file" "$key" "$content_type"
|
||||
upload "$file" "egbim/$key" "$content_type"
|
||||
done < <(find ./assets -type f -not -name '*.map' -print | sort)
|
||||
|
||||
echo "Uploaded static files to R2 bucket: $bucket"
|
||||
|
||||
@@ -16,6 +16,10 @@ export default {
|
||||
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)) {
|
||||
return json({ error: 'not_found' }, 404);
|
||||
}
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ ABC_IDEMPOTENCY_CONSUMER = "EGBIM_QA"
|
||||
PUBLIC_EXACT_PATHS = "/,/index.html,/favicon.ico"
|
||||
PUBLIC_PREFIXES = "/egbim/,/tova/,/gaia/,/shared/,/assets/,/auth/"
|
||||
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/"
|
||||
|
||||
[[r2_buckets]]
|
||||
|
||||
Reference in New Issue
Block a user