feat(mgmt-perf): 업로드 영역 하단 배치, 업로드 삭제 API, 앱 내 밝은 배경
- 대시보드 조회를 위·엑셀 업로드를 아래로 재배치 - DELETE /api/mgmt-perf/upload/:id 및 최근 업로드 행 삭제 버튼 - dashboard.css 전역 body 어두운 배경을 body.mgmt-perf-standalone로 한정, 임베드는 투명 - mgmt_perf_embed에 standalone 클래스 유지 Made-with: Cursor
This commit is contained in:
@@ -174,11 +174,48 @@ async function listUploads(pgPool, limit = 20) {
|
||||
return r.rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* 업로드 행 삭제(PG: 스냅샷은 ON DELETE CASCADE). 디스크의 업로드 파일도 제거합니다.
|
||||
* 파일 전용 모드: `id === "file"` 일 때 스냅샷 JSON만 삭제합니다.
|
||||
* @param {import("pg").Pool | null} pgPool
|
||||
* @param {string} uploadId UUID 또는 `"file"`
|
||||
*/
|
||||
async function deleteUpload(pgPool, uploadId) {
|
||||
const id = String(uploadId || "").trim();
|
||||
if (!id) return { ok: false, error: "id가 없습니다." };
|
||||
|
||||
if (!pgPool) {
|
||||
if (id !== "file") return { ok: false, error: "지원하지 않는 항목입니다." };
|
||||
try {
|
||||
if (fs.existsSync(FILE_STATE_PATH)) fs.unlinkSync(FILE_STATE_PATH);
|
||||
} catch (e) {
|
||||
return { ok: false, error: e.message || "삭제 실패" };
|
||||
}
|
||||
return { ok: true, fileBacked: true };
|
||||
}
|
||||
|
||||
const uuidRe = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||
if (!uuidRe.test(id)) return { ok: false, error: "잘못된 id입니다." };
|
||||
|
||||
const r = await pgPool.query(`DELETE FROM mgmt_perf_uploads WHERE id = $1::uuid RETURNING file_path`, [id]);
|
||||
if (r.rowCount === 0) return { ok: false, error: "항목을 찾을 수 없습니다." };
|
||||
const fp = r.rows[0].file_path;
|
||||
if (fp && fs.existsSync(fp)) {
|
||||
try {
|
||||
fs.unlinkSync(fp);
|
||||
} catch (_) {
|
||||
/* 로그만; DB는 이미 삭제됨 */
|
||||
}
|
||||
}
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
loadDefaultPayload,
|
||||
buildPayloadFromWorkbook,
|
||||
saveUploadAndSnapshot,
|
||||
getLatestPayloadRow,
|
||||
listUploads,
|
||||
deleteUpload,
|
||||
FILE_STATE_PATH,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user