feat(project): 업무·프로젝트 삭제를 '만든 사람'만 허용 + 경고
- 서버: api_delete_task/api_delete_project 가 created_by 일치자만 허용 (슈퍼관리자 예외, created_by 없는 과거 데이터는 관리자 허용). 403 반환. - 클라이언트: 삭제 버튼 클릭 시 만든 사람이 아니면 경고창 후 중단 (created_by 를 트리 삭제버튼·홈 카드·업무 객체로 전달) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+24
-1
@@ -62,6 +62,14 @@
|
||||
function initHome() {
|
||||
const grid = el("pj-project-grid");
|
||||
if (!grid && !el("pj-toggle-done")) return; // 홈 아님
|
||||
const wrap = document.querySelector(".pj-wrap");
|
||||
const me = ((wrap && wrap.dataset.me) || "").toLowerCase();
|
||||
const isSuper = !!(wrap && wrap.dataset.isSuper === "true");
|
||||
function canDelete(createdBy) {
|
||||
if (isSuper) return true;
|
||||
const cb = (createdBy || "").toLowerCase();
|
||||
return !!cb && cb === me;
|
||||
}
|
||||
|
||||
// ── 완료 프로젝트 숨기기 토글 (localStorage 기억) ──
|
||||
const toggleBtn = el("pj-toggle-done");
|
||||
@@ -140,6 +148,8 @@
|
||||
});
|
||||
el("pj-delete-project").addEventListener("click", async function () {
|
||||
const id = el("pj-e-id").value;
|
||||
const card = grid.querySelector('.pj-project-card[data-id="' + id + '"]');
|
||||
if (card && !canDelete(card.dataset.creator)) { alert("삭제는 만든 사람만 할 수 있습니다."); return; }
|
||||
if (!confirm("이 프로젝트를 삭제할까요? 안의 업무도 모두 삭제됩니다.")) return;
|
||||
try { await api("DELETE", "/project/api/projects/" + id); location.reload(); }
|
||||
catch (e) { alert("삭제 실패: " + e.message); }
|
||||
@@ -156,6 +166,15 @@
|
||||
const projectId = parseInt(root.dataset.projectId, 10);
|
||||
const canManage = root.dataset.canManage === "true";
|
||||
const isAdmin = root.dataset.isAdmin === "true";
|
||||
const me = (root.dataset.me || "").toLowerCase();
|
||||
const isSuper = root.dataset.isSuper === "true";
|
||||
// 삭제는 만든 사람만(슈퍼관리자 예외). created_by 없으면 서버가 최종 판정.
|
||||
function canDelete(createdBy) {
|
||||
if (isSuper) return true;
|
||||
const cb = (createdBy || "").toLowerCase();
|
||||
return !!cb && cb === me;
|
||||
}
|
||||
const DELETE_DENIED = "삭제는 만든 사람만 할 수 있습니다.";
|
||||
|
||||
const dataEl = el("pj-data");
|
||||
const data = JSON.parse(dataEl.textContent);
|
||||
@@ -918,7 +937,10 @@
|
||||
|
||||
el("pj-delete-task").addEventListener("click", async function () {
|
||||
const id = el("pj-t-id").value;
|
||||
if (!id || !confirm("이 업무를 삭제할까요?")) return;
|
||||
if (!id) return;
|
||||
const t = tasks.find(function (x) { return String(x.id) === String(id); });
|
||||
if (t && !canDelete(t.created_by)) { alert(DELETE_DENIED); return; }
|
||||
if (!confirm("이 업무를 삭제할까요?")) return;
|
||||
try {
|
||||
await api("DELETE", "/project/api/tasks/" + id);
|
||||
tasks = tasks.filter(function (x) { return String(x.id) !== String(id); });
|
||||
@@ -976,6 +998,7 @@
|
||||
document.querySelectorAll(".pj-tree-del").forEach(function (b) {
|
||||
b.addEventListener("click", async function (e) {
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
if (!canDelete(b.dataset.creator)) { alert(DELETE_DENIED); return; }
|
||||
if (!confirm("'" + (b.dataset.name || "") + "' 프로젝트를 삭제할까요? 안의 업무도 모두 삭제됩니다.")) return;
|
||||
try {
|
||||
await api("DELETE", "/project/api/projects/" + b.dataset.id);
|
||||
|
||||
Reference in New Issue
Block a user