- AWS SDK 사용으로 UTF-8 파일명 지원 (한글/특수문자) - 원본 파일명으로 S3 저장 및 다운로드 - multipart 헤더 UTF-8 디코딩 (파일명 깨짐 수정) - 드래그앤드롭 시각적 피드백 추가 - 모드/리소스팩/쉐이더 ABC순 정렬
183 lines
5 KiB
JavaScript
183 lines
5 KiB
JavaScript
import express from "express";
|
|
import { getTranslations, getIcons, getGamerules, dbPool } from "../lib/db.js";
|
|
import { getIconUrl } from "../lib/icons.js";
|
|
import {
|
|
MOD_API_URL,
|
|
fetchModStatus,
|
|
fetchMotd,
|
|
formatStatusForClient,
|
|
fetchAllPlayers,
|
|
fetchPlayerDetail,
|
|
getCachedStatus,
|
|
getCachedPlayers,
|
|
} from "../lib/minecraft.js";
|
|
|
|
const router = express.Router();
|
|
|
|
// 서버 상태 API
|
|
router.get("/status", async (req, res) => {
|
|
const cached = getCachedStatus();
|
|
if (cached) {
|
|
res.json(cached);
|
|
} else {
|
|
const [modStatus, motdData] = await Promise.all([
|
|
fetchModStatus(),
|
|
fetchMotd(),
|
|
]);
|
|
res.json(formatStatusForClient(modStatus, motdData));
|
|
}
|
|
});
|
|
|
|
// 플레이어 목록 API
|
|
router.get("/players", async (req, res) => {
|
|
const cached = getCachedPlayers();
|
|
if (cached.length > 0) {
|
|
res.json(cached);
|
|
} else {
|
|
const players = await fetchAllPlayers();
|
|
res.json(players);
|
|
}
|
|
});
|
|
|
|
// 번역 데이터 API
|
|
router.get("/translations", (req, res) => {
|
|
res.json(getTranslations());
|
|
});
|
|
|
|
// 아이콘 캐시 API
|
|
router.get("/icons", (req, res) => {
|
|
res.json(getIcons());
|
|
});
|
|
|
|
// 온디맨드 아이콘 API
|
|
router.get("/icon/:type/:name", async (req, res) => {
|
|
const { type, name } = req.params;
|
|
if (!["block", "item", "entity"].includes(type)) {
|
|
return res.status(400).json({ error: "Invalid type" });
|
|
}
|
|
const iconUrl = await getIconUrl(name, type);
|
|
if (iconUrl) {
|
|
res.json({ icon: iconUrl });
|
|
} else {
|
|
res.status(404).json({ error: "Icon not found" });
|
|
}
|
|
});
|
|
|
|
// 게임룰 API
|
|
router.get("/gamerules", (req, res) => {
|
|
res.json(getGamerules());
|
|
});
|
|
|
|
// 플레이어 통계 API
|
|
router.get("/player/:uuid/stats", async (req, res) => {
|
|
const { uuid } = req.params;
|
|
try {
|
|
const response = await fetch(`${MOD_API_URL}/player/${uuid}/stats`);
|
|
if (response.ok) {
|
|
res.json(await response.json());
|
|
} else {
|
|
res.status(404).json({ error: "통계를 불러올 수 없습니다" });
|
|
}
|
|
} catch (error) {
|
|
console.error("[ModAPI] 통계 조회 실패:", error.message);
|
|
res.status(500).json({ error: "서버 오류" });
|
|
}
|
|
});
|
|
|
|
// 플레이어 상세 정보 API
|
|
router.get("/player/:uuid", async (req, res) => {
|
|
const { uuid } = req.params;
|
|
const player = await fetchPlayerDetail(uuid);
|
|
if (player) {
|
|
res.json(player);
|
|
} else {
|
|
res.status(404).json({ error: "플레이어 데이터를 찾을 수 없습니다" });
|
|
}
|
|
});
|
|
|
|
// 월드 정보 API
|
|
router.get("/worlds", async (req, res) => {
|
|
try {
|
|
const response = await fetch(`${MOD_API_URL}/worlds`);
|
|
if (response.ok) {
|
|
res.json(await response.json());
|
|
} else {
|
|
res.json({ worlds: [] });
|
|
}
|
|
} catch (error) {
|
|
console.error("[ModAPI] 월드 조회 실패:", error.message);
|
|
res.json({ worlds: [] });
|
|
}
|
|
});
|
|
|
|
// 모드팩 목록 조회 API
|
|
router.get("/modpacks", async (req, res) => {
|
|
try {
|
|
const [rows] = await dbPool.query(`
|
|
SELECT id, name, version, minecraft_version, mod_loader, changelog,
|
|
file_size, contents_json, created_at, updated_at
|
|
FROM modpacks
|
|
ORDER BY created_at DESC
|
|
`);
|
|
|
|
// contents_json을 파싱하여 반환
|
|
const modpacks = rows.map((row) => ({
|
|
...row,
|
|
contents: row.contents_json ? JSON.parse(row.contents_json) : null,
|
|
contents_json: undefined, // 원본 JSON 문자열은 제거
|
|
}));
|
|
|
|
res.json(modpacks);
|
|
} catch (error) {
|
|
console.error("[API] 모드팩 목록 조회 실패:", error.message);
|
|
res.status(500).json({ error: "서버 오류" });
|
|
}
|
|
});
|
|
|
|
// 모드팩 다운로드 API (S3 리디렉션)
|
|
router.get("/modpacks/:id/download", async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
const [rows] = await dbPool.query(`SELECT * FROM modpacks WHERE id = ?`, [
|
|
id,
|
|
]);
|
|
if (rows.length === 0) {
|
|
return res.status(404).json({ error: "모드팩을 찾을 수 없습니다" });
|
|
}
|
|
|
|
const modpack = rows[0];
|
|
// file_key의 각 세그먼트를 인코딩
|
|
const encodedKey = modpack.file_key
|
|
.split("/")
|
|
.map((s) => encodeURIComponent(s))
|
|
.join("/");
|
|
const downloadUrl = `https://s3.caadiq.co.kr/minecraft/${encodedKey}`;
|
|
|
|
// file_key에서 파일명 추출 (인코딩 안 된 원본)
|
|
const filename = modpack.file_key.split("/").pop();
|
|
|
|
// S3에서 파일 가져오기
|
|
const s3Response = await fetch(downloadUrl);
|
|
if (!s3Response.ok) {
|
|
return res.status(404).json({ error: "파일을 찾을 수 없습니다" });
|
|
}
|
|
|
|
// 헤더 설정
|
|
res.setHeader("Content-Type", "application/zip");
|
|
res.setHeader("Content-Length", modpack.file_size);
|
|
res.setHeader(
|
|
"Content-Disposition",
|
|
`attachment; filename*=UTF-8''${encodeURIComponent(filename)}`
|
|
);
|
|
|
|
// 스트리밍 전달
|
|
const { Readable } = await import("stream");
|
|
Readable.fromWeb(s3Response.body).pipe(res);
|
|
} catch (error) {
|
|
console.error("[API] 모드팩 다운로드 실패:", error.message);
|
|
res.status(500).json({ error: "다운로드 실패" });
|
|
}
|
|
});
|
|
|
|
export default router;
|