feat: 파일 갤러리 CRUD PoC (DB + MinIO 동작 검증)

DB(메타데이터)와 MinIO(바이트)를 함께 실제로 쓰는 PoC. 신규 런타임 의존성 없음.
- src/files.js: files 테이블 + saveFile/list/get/rename/delete + getObjectStream
- src/server.js: POST/GET/PATCH/DELETE /files, 정적 UI, 부팅 시 테이블 보장
  · 업로드는 express.raw(멀티파트 파서 불필요), 다운로드는 S3 스트림 파이프
  · 텍스트 charset=utf-8 + RFC5987 파일명으로 한글 깨짐 방지
- public/index.html: 드래그앤드롭 업로드 + 목록/열기/이름변경/삭제 UI
- scripts/check-files.js(files:check): DB+S3 라운드트립 자가 검증

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-15 08:02:00 +00:00
parent 3252dfaa66
commit b85202ad1b
5 changed files with 555 additions and 4 deletions

49
scripts/check-files.js Normal file
View File

@@ -0,0 +1,49 @@
// DB+S3 라운드트립 한 방 검증: `npm run files:check`
// ensureFilesTable → saveFile → listFiles(포함 확인) → getObjectStream(바이트 일치) → 성공/실패.
import { pool } from "../src/db.js";
import {
ensureFilesTable,
saveFile,
listFiles,
getObjectStream,
} from "../src/files.js";
async function readStream(stream) {
const chunks = [];
for await (const c of stream) chunks.push(c);
return Buffer.concat(chunks);
}
try {
await ensureFilesTable();
const payload = Buffer.from(`poc check ${new Date().toISOString()}`);
const row = await saveFile({
name: "poc-check.txt",
contentType: "text/plain",
body: payload,
});
console.log("saveFile OK:", { id: row.id, key: row.key, size: row.size });
const files = await listFiles();
if (!files.some((f) => f.key === row.key)) {
throw new Error("listFiles 에 방금 업로드한 key 가 없음");
}
console.log("listFiles OK:", files.length, "rows");
const obj = await getObjectStream(row.key);
const got = await readStream(obj.body);
if (!got.equals(payload)) {
throw new Error(
`다운로드 바이트 불일치: expected ${payload.length}, got ${got.length}`
);
}
console.log("getObject OK: 바이트 일치", got.length, "bytes");
console.log("FILES CHECK OK ✅ (DB insert/select + MinIO put/get 라운드트립 성공)");
} catch (e) {
console.error("FILES CHECK FAIL:", e.message);
process.exitCode = 1;
} finally {
await pool.end();
}