This commit is contained in:
1 parent
a332ed0d8e
commit
efd1bf2e3c
6 files changed
+122
-24
No files matched your search
@@ -276,6 +276,9 @@ button { cursor: pointer; }
|
||||
.attachments h3 { margin: 0 0 8px; font-size: 14px; }
|
||||
.attachment-item { padding: 7px 0; color: #555; font-size: 13px; }
|
||||
.attachments a { color: var(--navy); text-decoration: underline; }
|
||||
.completion-notice { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin: 20px 22px 0; padding: 15px 17px; border: 1px solid #f0c45c; background: #fff9e8; color: #5b4500; }
|
||||
.completion-notice strong { display: block; margin-bottom: 4px; }
|
||||
.completion-notice p { margin: 0; font-size: 13px; }
|
||||
.detail-actions { display: flex; justify-content: space-between; padding: 23px 22px 0; }
|
||||
.egbim-page #backButton { display: none; }
|
||||
.egbim-page .form-footer:has(#backButton) { display: none; }
|
||||
|
||||
+66
-17
@@ -45,6 +45,11 @@
|
||||
return "";
|
||||
}
|
||||
|
||||
function toBoolean(value) {
|
||||
if (value === true || value === 1) return true;
|
||||
return ["true", "1", "yes", "y"].indexOf(String(value || "").trim().toLowerCase()) > -1;
|
||||
}
|
||||
|
||||
function normalizeCategory(value) {
|
||||
const raw = String(value || "").trim();
|
||||
return ({
|
||||
@@ -60,13 +65,17 @@
|
||||
function normalizeRemotePost(item) {
|
||||
item = item || {};
|
||||
const requester = item.requester || item.author || item.user || {};
|
||||
const fields = item.fields && typeof item.fields === "object" && !Array.isArray(item.fields) ? item.fields : {};
|
||||
const fields = [item.fields, item.metadata, item.meta, item.extra_fields, item.data].find(function (candidate) { return candidate && typeof candidate === "object" && !Array.isArray(candidate); }) || {};
|
||||
function value(keys) {
|
||||
return firstValue(item, keys) || firstValue(fields, keys) || firstValue(requester, keys);
|
||||
}
|
||||
const createdAt = value(["createdAt", "created_at", "date"]) || new Date().toISOString();
|
||||
const secret = value(["is_secret", "isSecret", "secret"]);
|
||||
const requesterEmail = value(["requester_email", "requesterEmail", "email"]);
|
||||
const completionRequested = value(["completion_requested", "completionRequested"]);
|
||||
const completionConfirmed = value(["completion_confirmed", "completionConfirmed"]);
|
||||
const completionRequestedAt = value(["completion_requested_at", "completionRequestedAt"]);
|
||||
const completionConfirmedAt = value(["completion_confirmed_at", "completionConfirmedAt"]);
|
||||
return {
|
||||
id: value(["id", "feedbackId", "feedback_id"]),
|
||||
feedbackId: value(["id", "feedbackId", "feedback_id"]),
|
||||
@@ -82,7 +91,11 @@
|
||||
date: String(createdAt).slice(0, 10),
|
||||
createdAt: createdAt,
|
||||
status: normalizeStatus(value(["feedback_status", "feedbackStatus", "status_code", "status"])),
|
||||
secret: secret === true || String(secret).toLowerCase() === "true" || String(secret) === "1",
|
||||
secret: toBoolean(secret),
|
||||
completionRequested: toBoolean(completionRequested) || Boolean(completionRequestedAt),
|
||||
completionRequestedAt: completionRequestedAt,
|
||||
completionConfirmed: toBoolean(completionConfirmed) || Boolean(completionConfirmedAt),
|
||||
completionConfirmedAt: completionConfirmedAt,
|
||||
attachments: value(["attachments", "images"]) || [],
|
||||
raw: item
|
||||
};
|
||||
@@ -356,7 +369,7 @@
|
||||
|
||||
async function requestJson(path, options) {
|
||||
const requestPath = String(path).split("?")[0];
|
||||
const sameOriginProxy = !config.apiBaseUrl && (requestPath === config.createFeedbackPath || requestPath === "/api/feedbacks" || /^\/api\/feedbacks\/[^\/]+(?:\/comments)?$/.test(requestPath));
|
||||
const sameOriginProxy = !config.apiBaseUrl && (requestPath === config.createFeedbackPath || requestPath === "/api/feedbacks" || /^\/api\/feedbacks\/[^\/]+(?:\/comments|\/completion-confirmation)?$/.test(requestPath));
|
||||
if (!config.apiBaseUrl && !sameOriginProxy) return { local: true };
|
||||
const target = config.apiBaseUrl ? config.apiBaseUrl.replace(/\/$/, '') + path : path;
|
||||
const response = await fetch(target, Object.assign({ credentials: 'include' }, options));
|
||||
@@ -529,15 +542,15 @@
|
||||
if (detailListLink) detailListLink.textContent = '목록으로';
|
||||
const bottomBackButton = qs('#backButton', detail);
|
||||
if (bottomBackButton && bottomBackButton.parentElement) bottomBackButton.parentElement.remove();
|
||||
const id = new URLSearchParams(window.location.search).get('id') || 'EG-1048';
|
||||
let post = getPosts().find(function (item) { return item.id === id; });
|
||||
if (!post) {
|
||||
try {
|
||||
const result = await requestJson("/api/feedbacks/" + encodeURIComponent(id), { method: "GET" });
|
||||
post = normalizeRemotePost(result.feedback || result.data || result);
|
||||
} catch (error) {
|
||||
console.warn("[QA] feedback detail load failed:", error.message);
|
||||
}
|
||||
const id = new URLSearchParams(window.location.search).get("id") || "EG-1048";
|
||||
const storedPost = getPosts().find(function (item) { return item.id === id; });
|
||||
let post = storedPost;
|
||||
try {
|
||||
const result = await requestJson("/api/feedbacks/" + encodeURIComponent(id), { method: "GET" });
|
||||
const remotePost = normalizeRemotePost(result.feedback || result.data || result);
|
||||
if (remotePost.id) post = Object.assign({}, storedPost || {}, remotePost);
|
||||
} catch (error) {
|
||||
console.warn("[QA] feedback detail load failed:", error.message);
|
||||
}
|
||||
if (!post) {
|
||||
qs('[data-detail-title]').textContent = '문의 내용을 찾을 수 없습니다.';
|
||||
@@ -546,10 +559,18 @@
|
||||
}
|
||||
qs('[data-detail-category]').textContent = post.category;
|
||||
qs('[data-detail-title]').textContent = post.title;
|
||||
qs('[data-detail-status]').textContent = normalizeStatus(post.status) || '공지';
|
||||
const renderDetailStatus = function () {
|
||||
const statusElement = qs("[data-detail-status]");
|
||||
const status = normalizeStatus(post.status) || "공지";
|
||||
if (!statusElement) return;
|
||||
statusElement.textContent = status;
|
||||
statusElement.classList.toggle("done", status === "완료");
|
||||
statusElement.classList.toggle("review", status !== "완료");
|
||||
};
|
||||
renderDetailStatus();
|
||||
qs('[data-detail-date]').textContent = post.date;
|
||||
qs('[data-detail-author]').textContent = post.author;
|
||||
const detailUser = getAuthUser();
|
||||
let detailUser = getAuthUser();
|
||||
qs('[data-detail-company]').textContent = getPostCompanyLabel(post, detailUser);
|
||||
qs('[data-detail-department]').textContent = post.department;
|
||||
qs('[data-detail-content]').textContent = post.content;
|
||||
@@ -563,13 +584,41 @@
|
||||
const deleteButton = qs('#deleteButton');
|
||||
const editForm = qs('#editForm');
|
||||
const editError = qs('#editError');
|
||||
const completionNotice = qs("#completionNotice");
|
||||
const completionButton = qs("#completionButton");
|
||||
const renderCompletionNotice = function () {
|
||||
const owner = isPostOwner(post, detailUser || getAuthUser());
|
||||
const visible = owner && post.completionRequested && !post.completionConfirmed;
|
||||
if (completionNotice) completionNotice.hidden = !visible;
|
||||
};
|
||||
const ownerActions = function () {
|
||||
const owner = isPostOwner(post, getAuthUser());
|
||||
const owner = isPostOwner(post, detailUser || getAuthUser());
|
||||
if (editButton) editButton.hidden = !owner;
|
||||
if (deleteButton) deleteButton.hidden = !owner;
|
||||
renderCompletionNotice();
|
||||
};
|
||||
ownerActions();
|
||||
if (authReady) authReady.then(ownerActions);
|
||||
if (authReady) authReady.then(function (user) { detailUser = user || detailUser; ownerActions(); renderCompletionNotice(); });
|
||||
if (completionButton) completionButton.addEventListener("click", async function () {
|
||||
if (!post.completionRequested || post.completionConfirmed) return;
|
||||
completionButton.disabled = true;
|
||||
completionButton.textContent = "확인 처리 중...";
|
||||
try {
|
||||
await requestJson("/api/feedbacks/" + encodeURIComponent(post.id) + "/completion-confirmation", { method: "POST" });
|
||||
post.completionRequested = false;
|
||||
post.completionConfirmed = true;
|
||||
post.status = "완료";
|
||||
updateStoredPost(post);
|
||||
renderDetailStatus();
|
||||
renderCompletionNotice();
|
||||
showToast("문의가 완료 처리되었습니다.");
|
||||
} catch (error) {
|
||||
showToast(error.message || "문의 완료 확인에 실패했습니다.");
|
||||
} finally {
|
||||
completionButton.disabled = false;
|
||||
completionButton.textContent = "완료 확인";
|
||||
}
|
||||
});
|
||||
if (editButton && editForm) editButton.addEventListener('click', function () {
|
||||
qs('#editCategory').value = post.category || '일반문의';
|
||||
qs('#editTitle').value = post.title || '';
|
||||
@@ -741,7 +790,7 @@
|
||||
}).join('');
|
||||
});
|
||||
refreshRemoteStatus(post).then(function (changed) {
|
||||
if (changed) qs('[data-detail-status]').textContent = normalizeStatus(post.status) || '공지';
|
||||
if (changed) renderDetailStatus();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -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?v=20260921-4"></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?v=20260929-1"></head>
|
||||
<body>
|
||||
<main class="egbim-page"><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><nav class="sub-nav" aria-label="EG-BIM 지원 메뉴"><div class="sub-nav-inner"><a href="index.html" aria-current="page">문의하기(Q&A)</a><a href="#" aria-disabled="true" class="disabled-tab">원격지원</a></div></nav></section>
|
||||
<section class="page-shell" id="detail"><div class="section-heading"><div><h2>문의 상세</h2><p>문의 내용과 답변을 확인할 수 있습니다.</p></div></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><div data-detail-attachments><span class="help-text">첨부된 파일이 없습니다.</span></div></div><div class="detail-actions"><div class="owner-actions"><button class="button button-outline" id="editButton" type="button" hidden>수정</button><button class="button button-danger" id="deleteButton" type="button" hidden>삭제</button></div><a class="button button-outline" href="index.html">목록</a></div></article><form id="editForm" class="edit-form" hidden><h3>문의 수정</h3><div class="form-row"><label class="form-label" for="editCategory">구분</label><div class="form-control"><select id="editCategory" class="select"><option value="오류문의">오류문의</option><option value="개선문의">개선문의</option><option value="일반문의">일반문의</option></select></div></div><div class="form-row"><label class="form-label" for="editTitle">제목</label><div class="form-control"><input id="editTitle" class="input" maxlength="200" required></div></div><div class="form-row"><label class="form-label" for="editContent">내용</label><div class="form-control column"><textarea id="editContent" class="textarea" maxlength="10000" required></textarea></div></div><div class="form-row"><div class="form-label">설정</div><div class="form-control"><label class="check"><input id="editSecret" type="checkbox"> 비밀글</label></div></div><div class="form-footer"><button class="button button-outline" id="editCancel" type="button">취소</button><button class="button button-primary" type="submit">수정 저장</button></div><div id="editError" class="form-error" role="alert" hidden></div></form><section class="comment-box"><h3>답변 <span id="commentCount">0</span></h3><div id="comments"></div><form id="commentForm" class="comment-form"><label class="sr-only" for="commentContent">댓글 내용</label><textarea id="commentContent" class="textarea" maxlength="5000" placeholder="답변 또는 추가 문의를 입력해주세요" required></textarea><div class="comment-attachment-input"><label class="button button-primary comment-image-button" for="commentImages">이미지 첨부</label><input id="commentImages" type="file" accept="image/*" multiple><div id="commentImagePreview" class="comment-image-preview"></div></div><div class="comment-form-actions"><span id="commentError" class="form-error" role="alert" hidden></span><button class="button button-primary" id="commentSubmit" type="submit">댓글 등록</button></div></form></section><div class="form-footer"><button class="button button-outline" id="backButton" type="button">목록으로</button></div></section>
|
||||
</main><script src="config.js?v=20260921-3"></script><script src="app.js?v=20260928-3"></script>
|
||||
<section class="page-shell" id="detail"><div class="section-heading"><div><h2>문의 상세</h2><p>문의 내용과 답변을 확인할 수 있습니다.</p></div></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><div data-detail-attachments><span class="help-text">첨부된 파일이 없습니다.</span></div></div><div id="completionNotice" class="completion-notice" hidden><div><strong>처리 완료 안내</strong><p>담당자가 처리를 완료했습니다. 내용을 확인하셨다면 완료 확인을 눌러주세요.</p></div><button class="button button-primary" id="completionButton" type="button">완료 확인</button></div><div class="detail-actions"><div class="owner-actions"><button class="button button-outline" id="editButton" type="button" hidden>수정</button><button class="button button-danger" id="deleteButton" type="button" hidden>삭제</button></div><a class="button button-outline" href="index.html">목록</a></div></article><form id="editForm" class="edit-form" hidden><h3>문의 수정</h3><div class="form-row"><label class="form-label" for="editCategory">구분</label><div class="form-control"><select id="editCategory" class="select"><option value="오류문의">오류문의</option><option value="개선문의">개선문의</option><option value="일반문의">일반문의</option></select></div></div><div class="form-row"><label class="form-label" for="editTitle">제목</label><div class="form-control"><input id="editTitle" class="input" maxlength="200" required></div></div><div class="form-row"><label class="form-label" for="editContent">내용</label><div class="form-control column"><textarea id="editContent" class="textarea" maxlength="10000" required></textarea></div></div><div class="form-row"><div class="form-label">설정</div><div class="form-control"><label class="check"><input id="editSecret" type="checkbox"> 비밀글</label></div></div><div class="form-footer"><button class="button button-outline" id="editCancel" type="button">취소</button><button class="button button-primary" type="submit">수정 저장</button></div><div id="editError" class="form-error" role="alert" hidden></div></form><section class="comment-box"><h3>답변 <span id="commentCount">0</span></h3><div id="comments"></div><form id="commentForm" class="comment-form"><label class="sr-only" for="commentContent">댓글 내용</label><textarea id="commentContent" class="textarea" maxlength="5000" placeholder="답변 또는 추가 문의를 입력해주세요" required></textarea><div class="comment-attachment-input"><label class="button button-primary comment-image-button" for="commentImages">이미지 첨부</label><input id="commentImages" type="file" accept="image/*" multiple><div id="commentImagePreview" class="comment-image-preview"></div></div><div class="comment-form-actions"><span id="commentError" class="form-error" role="alert" hidden></span><button class="button button-primary" id="commentSubmit" type="submit">댓글 등록</button></div></form></section><div class="form-footer"><button class="button button-outline" id="backButton" type="button">목록으로</button></div></section>
|
||||
</main><script src="config.js?v=20260921-3"></script><script src="app.js?v=20260929-1"></script>
|
||||
</body></html>
|
||||
+2
-2
@@ -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?v=20260921-4">
|
||||
<link rel="stylesheet" href="../assets/styles.css?v=20260929-1">
|
||||
</head>
|
||||
<body>
|
||||
<main class="qa-list-page">
|
||||
@@ -23,6 +23,6 @@
|
||||
</section>
|
||||
</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=20260928-3"></script>
|
||||
<script src="config.js?v=20260921-3"></script><script src="app.js?v=20260929-1"></script>
|
||||
</body>
|
||||
</html>
|
||||
+2
-2
@@ -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?v=20260921-4"></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?v=20260929-1"></head>
|
||||
<body>
|
||||
<main class="egbim-page"><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><nav class="sub-nav" aria-label="EG-BIM 지원 메뉴"><div class="sub-nav-inner"><a href="index.html" aria-current="page">문의하기(Q&A)</a><a href="#" aria-disabled="true" class="disabled-tab">원격지원</a></div></nav></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">파일은 문의와 함께 안전하게 업로드됩니다. 파일당 최대 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="config.js?v=20260921-3"></script><script src="app.js?v=20260928-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=20260929-1"></script>
|
||||
</body></html>
|
||||
@@ -14,6 +14,7 @@ export default {
|
||||
if (request.method === 'GET') return listFeedbacks(request, env, url);
|
||||
return createFeedback(request, env);
|
||||
}
|
||||
if (/^\/api\/feedbacks\/[^/]+\/completion-confirmation$/.test(url.pathname)) return completionConfirmation(request, env, url);
|
||||
if (/^\/api\/feedbacks\/[^/]+\/comments$/.test(url.pathname)) return feedbackComments(request, env, url);
|
||||
if (/^\/api\/feedbacks\/[^/]+$/.test(url.pathname)) return feedbackDetail(request, env, url);
|
||||
|
||||
@@ -484,6 +485,51 @@ async function listFeedbacks(request, env, url) {
|
||||
return json({ items: Array.isArray(items) ? items : [], meta });
|
||||
}
|
||||
|
||||
async function completionConfirmation(request, env, url) {
|
||||
if (request.method !== "POST") return json({ error: "method_not_allowed" }, 405, { allow: "POST" });
|
||||
const user = await getSession(request, env);
|
||||
if (!user) return json({ error: "unauthenticated" }, 401);
|
||||
if (!env.ABC_API_KEY) return json({ error: "feedback_api_not_configured" }, 503);
|
||||
if (!env.ABC_PROJECT_ID || !env.ABC_CHANNEL_ID) return json({ error: "feedback_target_not_configured" }, 503);
|
||||
|
||||
const feedbackId = decodeURIComponent(url.pathname.split("/")[3] || "");
|
||||
if (!isUuidv7(feedbackId)) return json({ error: "invalid_feedback_id" }, 400);
|
||||
|
||||
const requesterId = String(user.requesterId || user.ssoSubject || user.userUuid || "");
|
||||
const requesterTenantId = String(user.requesterTenantId || user.tenantId || "");
|
||||
if (!requesterId || !requesterTenantId) return json({ error: "requester_identity_missing" }, 422);
|
||||
|
||||
const apiBaseUrlValue = String(env.ABC_API_BASE_URL || "https://feedback.hmac.kr");
|
||||
const apiBaseUrl = apiBaseUrlValue.endsWith("/") ? apiBaseUrlValue.slice(0, -1) : apiBaseUrlValue;
|
||||
const endpoint = apiBaseUrl + "/api/projects/" + encodeURIComponent(env.ABC_PROJECT_ID) + "/channels/" + encodeURIComponent(env.ABC_CHANNEL_ID) + "/feedbacks/" + encodeURIComponent(feedbackId) + "/completion-confirmation";
|
||||
const idempotencyConsumer = env.ABC_IDEMPOTENCY_CONSUMER || env.ABC_SOURCE_NAMESPACE || "EGBIM_QA";
|
||||
const headers = {
|
||||
accept: "application/json",
|
||||
"content-type": "application/json",
|
||||
"x-api-key": env.ABC_API_KEY,
|
||||
"x-requester-id": requesterId,
|
||||
"x-requester-tenant-id": requesterTenantId,
|
||||
"x-idempotency-consumer": idempotencyConsumer,
|
||||
"idempotency-key": feedbackId,
|
||||
"x-source-namespace": idempotencyConsumer,
|
||||
"x-source-record-id": feedbackId
|
||||
};
|
||||
const body = JSON.stringify({ requester_id: requesterId, requester_tenant_id: requesterTenantId });
|
||||
let response;
|
||||
try {
|
||||
response = await fetch(endpoint, { method: "POST", headers, body });
|
||||
} catch (error) {
|
||||
console.error("feedback_completion_confirmation_request_failed", error instanceof Error ? error.message : "unknown");
|
||||
return json({ error: "feedback_api_unreachable" }, 502);
|
||||
}
|
||||
const result = await response.json().catch(() => ({}));
|
||||
if (!response.ok) {
|
||||
const upstreamMessage = String(result.message || result.error || result.code || "").slice(0, 240);
|
||||
return json({ error: "feedback_completion_confirmation_rejected", message: upstreamMessage || "문의 완료 확인에 실패했습니다.", status: response.status }, response.status >= 500 ? 502 : response.status);
|
||||
}
|
||||
return json({ ok: true, feedback: result.feedback || result.data || result });
|
||||
}
|
||||
|
||||
async function feedbackComments(request, env, url) {
|
||||
if (request.method !== 'GET' && request.method !== 'POST') return json({ error: 'method_not_allowed' }, 405, { allow: 'GET, POST' });
|
||||
const user = await getSession(request, env);
|
||||
|
||||
Reference in new issue
Block a user