fix: show post and comment actions only to the verified author
Deploy EG-BIM QA Gateway / deploy (push) Successful in 40s
Deploy EG-BIM QA Gateway / deploy (push) Successful in 40s
This commit is contained in:
1 parent
688d16555e
commit
dbf5dc3611
8 files changed
+89
-30
No files matched your search
+25
-20
@@ -266,13 +266,16 @@
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(posts));
|
||||
}
|
||||
|
||||
function isPostOwner(post, user) {
|
||||
if (!post || !user) return false;
|
||||
function matchesAuthorId(authorId, user) {
|
||||
if (!user) return false;
|
||||
const normalizeId = function (id) { return String(id || '').trim().toLowerCase(); };
|
||||
const postIds = [post.requesterId, post.requesterUuid].map(normalizeId).filter(Boolean);
|
||||
const userIds = [user.requesterId, user.ssoSubject, user.userUuid].map(normalizeId).filter(Boolean);
|
||||
if (postIds.length && userIds.length) return postIds.some(function (id) { return userIds.indexOf(id) > -1; });
|
||||
return Boolean(post.author && [user.name, user.loginId, user.email].filter(Boolean).indexOf(post.author) > -1);
|
||||
const ownerId = normalizeId(authorId);
|
||||
const userId = normalizeId(user.requesterId || user.ssoSubject || user.userUuid);
|
||||
return Boolean(ownerId && userId && ownerId === userId);
|
||||
}
|
||||
|
||||
function isPostOwner(post, user) {
|
||||
return Boolean(post) && matchesAuthorId(post.requesterId || post.requesterUuid, user);
|
||||
}
|
||||
|
||||
function normalizeStatus(value) {
|
||||
@@ -405,15 +408,15 @@
|
||||
if (tenantId && tenantIds.indexOf(tenantId) === -1) tenantIds.unshift(tenantId);
|
||||
const primaryTenant = tenantList.find(function (tenant) { return typeof tenant !== 'string' && (tenant.id || tenant.tenantId) === tenantId; }) || tenantList[0] || {};
|
||||
const tenantName = raw.tenantName || raw.tenant_name || custom.tenantName || custom.tenant_name || claims.tenantName || claims.tenant_name || getTenantName(primaryTenant);
|
||||
const ssoSubject = raw.ssoSubject || raw.oauthSubject || raw.subject || claims.sub || claims.subject || raw.userId || sessionStorage.getItem('descopeUserId') || '';
|
||||
const userUuid = raw.userUuid || raw.userId || claims.userId || claims.user_id || sessionStorage.getItem('descopeUserId') || '';
|
||||
const ssoSubject = raw.ssoSubject || raw.oauthSubject || raw.subject || claims.sub || claims.subject || raw.requesterId || raw.userUuid || raw.userId || sessionStorage.getItem('descopeUserId') || '';
|
||||
const userUuid = raw.userUuid || raw.userId || claims.userId || claims.user_id || ssoSubject || sessionStorage.getItem('descopeUserId') || '';
|
||||
const loginId = raw.loginId || (raw.loginIds || [])[0] || raw.email || claims.email || claimProfile.email || sessionStorage.getItem('loginId') || readCookie('descope_login_id') || '';
|
||||
const email = raw.email || claims.email || claimProfile.email || sessionStorage.getItem('loginId') || '';
|
||||
const company = raw.company || custom.company || claims.company || sessionStorage.getItem('company') || getCompanyNameFromEmail(email) || '';
|
||||
return {
|
||||
userUuid: userUuid,
|
||||
ssoSubject: ssoSubject,
|
||||
requesterId: ssoSubject,
|
||||
requesterId: raw.requesterId || ssoSubject,
|
||||
tenantId: tenantId,
|
||||
requesterTenantId: tenantId,
|
||||
tenantIds: tenantIds,
|
||||
@@ -463,7 +466,12 @@
|
||||
const response = await fetch(config.ssoSessionEndpoint, { credentials: 'include', headers: { Accept: 'application/json' } });
|
||||
if (!response.ok) throw new Error('SSO 세션 확인에 실패했습니다.');
|
||||
const body = await response.json();
|
||||
const remoteUser = normalizeAuthUser(body.user || body, body.claims || body.scope || {});
|
||||
const rawUser = body.user || body;
|
||||
if (body.authenticated === false || !rawUser || !(rawUser.requesterId || rawUser.ssoSubject || rawUser.userUuid || rawUser.userId || rawUser.subject)) {
|
||||
resolvedAuthUser = null;
|
||||
return null;
|
||||
}
|
||||
const remoteUser = normalizeAuthUser(rawUser, body.claims || body.scope || {});
|
||||
if (remoteUser.loginId || remoteUser.ssoSubject) {
|
||||
resolvedAuthUser = remoteUser;
|
||||
return remoteUser;
|
||||
@@ -471,8 +479,8 @@
|
||||
} catch (error) {
|
||||
console.warn('[QA] SSO session bridge unavailable:', error.message);
|
||||
}
|
||||
resolvedAuthUser = localUser;
|
||||
return localUser;
|
||||
resolvedAuthUser = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
function renderAuth(user) {
|
||||
@@ -727,7 +735,7 @@
|
||||
renderDetailStatus();
|
||||
qs('[data-detail-date]').textContent = post.dateTime || post.date;
|
||||
qs('[data-detail-author]').textContent = post.author;
|
||||
let detailUser = getAuthUser();
|
||||
const detailUser = await (authReady || Promise.resolve(getAuthUser()));
|
||||
qs('[data-detail-company]').textContent = getPostCompanyLabel(post, detailUser);
|
||||
qs('[data-detail-department]').textContent = post.department;
|
||||
qs('[data-detail-content]').textContent = post.content;
|
||||
@@ -747,20 +755,19 @@
|
||||
const completionDoneNotice = qs("#completionDoneNotice");
|
||||
const completionButton = qs("#completionButton");
|
||||
const renderCompletionNotice = function () {
|
||||
const owner = isPostOwner(post, detailUser || getAuthUser());
|
||||
const owner = isPostOwner(post, detailUser);
|
||||
const completed = normalizeStatus(post.status) === "완료" || post.completionConfirmed;
|
||||
const visible = owner && !completed && post.completionRequested;
|
||||
if (completionNotice) completionNotice.hidden = !visible;
|
||||
if (completionDoneNotice) completionDoneNotice.hidden = !(owner && completed);
|
||||
};
|
||||
const ownerActions = function () {
|
||||
const owner = isPostOwner(post, detailUser || getAuthUser());
|
||||
const owner = isPostOwner(post, detailUser);
|
||||
if (editButton) editButton.hidden = !owner;
|
||||
if (deleteButton) deleteButton.hidden = !owner;
|
||||
renderCompletionNotice();
|
||||
};
|
||||
ownerActions();
|
||||
if (authReady) authReady.then(function (user) { detailUser = user || detailUser; ownerActions(); renderCompletionNotice(); comments = renderComments(comments); });
|
||||
if (completionButton) completionButton.addEventListener("click", async function () {
|
||||
if (!post.completionRequested || post.completionConfirmed) return;
|
||||
completionButton.disabled = true;
|
||||
@@ -890,15 +897,13 @@
|
||||
|
||||
let editingCommentId = null;
|
||||
function isCommentOwner(comment, user) {
|
||||
const authorId = String(comment.authorId || '').trim().toLowerCase();
|
||||
const userIds = [user && user.requesterId, user && user.ssoSubject, user && user.userUuid].map(function (id) { return String(id || '').trim().toLowerCase(); }).filter(Boolean);
|
||||
return Boolean(authorId && userIds.indexOf(authorId) !== -1);
|
||||
return matchesAuthorId(comment.authorId, user);
|
||||
}
|
||||
|
||||
function renderComments(comments) {
|
||||
comments = (comments || []).map(normalizeComment).filter(function (comment) { return comment.content; });
|
||||
qs('#commentCount').textContent = comments.length;
|
||||
const user = detailUser || getAuthUser();
|
||||
const user = detailUser;
|
||||
qs('#comments').innerHTML = comments.length ? comments.map(function (comment) {
|
||||
const owner = Boolean(comment.id) && isCommentOwner(comment, user);
|
||||
const editing = Boolean(editingCommentId) && editingCommentId === comment.id && owner;
|
||||
|
||||
+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=20261030-search-1"></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=20261030-owner-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 id="completionNotice" class="completion-notice" hidden><div><strong>처리 완료 안내</strong><p>담당자가 처리를 완료했습니다. 내용을 확인하셨다면 완료 확인을 눌러주세요.</p></div><button class="button button-primary" id="completionButton" type="button">완료 확인</button></div><div id="completionDoneNotice" class="completion-done-notice" hidden>완료 처리 된 요청 건입니다.</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=20261030-search-1"></script>
|
||||
</main><script src="config.js?v=20260921-3"></script><script src="app.js?v=20261030-owner-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=20261030-search-1">
|
||||
<link rel="stylesheet" href="../assets/styles.css?v=20261030-owner-1">
|
||||
</head>
|
||||
<body>
|
||||
<main class="qa-list-page">
|
||||
@@ -24,6 +24,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=20261030-search-1"></script>
|
||||
<script src="config.js?v=20260921-3"></script><script src="app.js?v=20261030-owner-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=20261030-search-1"></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=20261030-owner-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=20261030-search-1"></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=20261030-owner-1"></script>
|
||||
</body></html>
|
||||
Reference in new issue
Block a user