feat(projects): 등록한 프로젝트 수정 기능 추가
- 상세(모달/단독) 소유자 영역에 '수정' 버튼 → 등록 모달을 현재 값으로 채워 재사용 - POST /projects/:id/edit 라우트 + db.updateProject(소유자 조건으로 타인 수정 차단) - 등록/수정 공용 모달을 _project_form_modal.ejs 로 추출(dashboard·project 에서 include) - pencil 아이콘 추가, modals.js 에 openEdit/openShare 초기화 로직 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
11
src/db.js
11
src/db.js
@@ -116,6 +116,17 @@ export async function addProject({ owner, title, description, repoUrl, appUrl, t
|
||||
return rows[0].id;
|
||||
}
|
||||
|
||||
// 본인 프로젝트 수정. 적용 행 수 반환(0이면 권한 없음/없음). 소유자 조건으로 타인 수정 차단.
|
||||
export async function updateProject({ id, sabun, title, description, repoUrl, appUrl, tags, isPublic, lang }) {
|
||||
const { rowCount } = await q(
|
||||
`UPDATE projects
|
||||
SET title=$3, description=$4, repo_url=$5, app_url=$6, tags=$7, is_public=$8, lang=$9, updated_at=now()
|
||||
WHERE id=$1 AND owner_sabun=$2`,
|
||||
[id, sabun, title, description || "", repoUrl || "", appUrl || "", tags || "", isPublic !== false, lang || ""]
|
||||
);
|
||||
return rowCount;
|
||||
}
|
||||
|
||||
// 본인 프로젝트 삭제. 삭제 행 수 반환(0이면 권한 없음/없음).
|
||||
export async function deleteProject({ id, sabun }) {
|
||||
const { rowCount } = await q(
|
||||
|
||||
@@ -28,6 +28,7 @@ const OCTICONS = {
|
||||
ext: '<path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z"/>',
|
||||
trash: '<path d="M11 1.75V3h2.25a.75.75 0 0 1 0 1.5H2.75a.75.75 0 0 1 0-1.5H5V1.75C5 .784 5.784 0 6.75 0h2.5C10.216 0 11 .784 11 1.75ZM4.496 6.675l.66 6.6a.25.25 0 0 0 .249.225h5.19a.25.25 0 0 0 .249-.225l.66-6.6a.75.75 0 0 1 1.492.149l-.66 6.6A1.748 1.748 0 0 1 10.595 15h-5.19a1.75 1.75 0 0 1-1.741-1.575l-.66-6.6a.75.75 0 1 1 1.492-.15ZM6.5 1.75V3h3V1.75a.25.25 0 0 0-.25-.25h-2.5a.25.25 0 0 0-.25.25Z"/>',
|
||||
book: '<path d="M0 1.75A.75.75 0 0 1 .75 1h4.253c1.227 0 2.317.59 3 1.501A3.743 3.743 0 0 1 11.006 1h4.245a.75.75 0 0 1 .75.75v10.5a.75.75 0 0 1-.75.75h-4.507a2.25 2.25 0 0 0-1.591.659l-.622.621a.75.75 0 0 1-1.06 0l-.622-.621A2.25 2.25 0 0 0 5.258 13H.75a.75.75 0 0 1-.75-.75Zm7.251 10.324.004-5.073-.002-2.253A2.25 2.25 0 0 0 5.003 2.5H1.5v9h3.757a3.75 3.75 0 0 1 1.994.574ZM8.755 4.75l-.004 7.322a3.752 3.752 0 0 1 1.992-.572H14.5v-9h-3.495a2.25 2.25 0 0 0-2.25 2.25Z"/>',
|
||||
pencil: '<path d="M11.013 1.427a1.75 1.75 0 0 1 2.474 0l1.086 1.086a1.75 1.75 0 0 1 0 2.474l-8.61 8.61c-.21.21-.47.364-.756.445l-3.251.93a.75.75 0 0 1-.927-.928l.929-3.25c.081-.286.235-.547.445-.758l8.61-8.61Zm.176 4.823L9.75 4.81l-6.286 6.287a.253.253 0 0 0-.064.108l-.558 1.953 1.953-.558a.253.253 0 0 0 .108-.064Zm1.238-3.763a.25.25 0 0 0-.354 0L10.811 3.75l1.439 1.44 1.263-1.263a.25.25 0 0 0 0-.354Z"/>',
|
||||
};
|
||||
|
||||
// 인라인 SVG 아이콘 문자열. fill 기본은 currentColor(부모 색 상속).
|
||||
|
||||
@@ -179,6 +179,36 @@ app.post("/projects", requireAuth, async (req, res) => {
|
||||
res.redirect("/?toast=" + encodeURIComponent(msg));
|
||||
});
|
||||
|
||||
// --- 프로젝트 수정 (본인) --- 등록과 동일한 필드 파싱. 소유자 조건은 db.updateProject 에서 강제.
|
||||
app.post("/projects/:id/edit", requireAuth, async (req, res) => {
|
||||
const { title, description, repo_url, app_url, tags } = req.body;
|
||||
const back = safeBack(req.body.return_to, "/");
|
||||
const sep = back.includes("?") ? "&" : "?";
|
||||
if (!title || !title.trim()) {
|
||||
return res.redirect(back + sep + "toast=" + encodeURIComponent("제목을 입력해주세요"));
|
||||
}
|
||||
const tagArr = String(tags || "")
|
||||
.split(",")
|
||||
.map((t) => t.trim().replace(/^#/, ""))
|
||||
.filter(Boolean)
|
||||
.slice(0, 5);
|
||||
const repoUrl = (repo_url || "").trim();
|
||||
const lang = (req.body.lang || "").trim() || (await fetchMainLang(repoUrl)) || langFromTags(tagArr);
|
||||
const n = await db.updateProject({
|
||||
id: req.params.id,
|
||||
sabun: req.session.user.sabun,
|
||||
title: title.trim(),
|
||||
description: (description || "").trim(),
|
||||
repoUrl,
|
||||
appUrl: (app_url || "").trim(),
|
||||
tags: tagArr.join(", "),
|
||||
isPublic: req.body.is_public !== "0",
|
||||
lang,
|
||||
});
|
||||
const msg = n ? "수정되었습니다" : "수정 권한이 없습니다";
|
||||
res.redirect(back + sep + "toast=" + encodeURIComponent(msg));
|
||||
});
|
||||
|
||||
// --- 단독 상세 페이지 (모달의 비-JS 폴백 / 공유 링크) ---
|
||||
app.get("/projects/:id", requireAuth, async (req, res) => {
|
||||
const me = req.session.user.sabun;
|
||||
|
||||
Reference in New Issue
Block a user