minecraft-web/backend/lib/icons.js
caadiq 10c27eecba feat: blocks/items 테이블 통합 및 아이콘 시스템 개선
- blocks 테이블을 items 테이블로 통합 (type 컬럼 추가)
- 아이콘 조회 시 DB에서 먼저 검색하여 모드 아이템 지원
- S3 경로 통일: icons/items/<modid>_<name>.png
- 번역 업로드/삭제 API 수정
- 번역 캐시 로드 로직 수정
2025-12-26 19:52:35 +09:00

115 lines
3.4 KiB
JavaScript

import path from "path";
import { fileURLToPath } from "url";
import { readFileSync } from "fs";
import https from "https";
import http from "http";
import { dbPool, getIcons, setIconCache } from "./db.js";
import { s3Config, uploadToS3 } from "./s3.js";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// MHF UUID 매핑 로드
const entityToMHF = JSON.parse(
readFileSync(path.join(__dirname, "..", "data", "entityToMHF.json"), "utf-8")
);
/**
* URL에서 이미지 다운로드
*/
function downloadImage(url) {
return new Promise((resolve, reject) => {
const protocol = url.startsWith("https") ? https : http;
protocol
.get(url, (res) => {
if (res.statusCode === 301 || res.statusCode === 302) {
return downloadImage(res.headers.location)
.then(resolve)
.catch(reject);
}
if (res.statusCode !== 200) {
reject(new Error(`HTTP ${res.statusCode}`));
return;
}
const chunks = [];
res.on("data", (chunk) => chunks.push(chunk));
res.on("end", () => resolve(Buffer.concat(chunks)));
res.on("error", reject);
})
.on("error", reject);
});
}
/**
* 온디맨드 아이콘 가져오기 (없으면 다운로드/저장)
* 모드 아이템도 지원 - DB에서 먼저 검색
*/
async function getIconUrl(name, type) {
const iconsCache = getIcons();
// 캐시 키: type:name 형식
const cacheKey = `${type}:${name}`;
// 캐시에 있으면 반환
if (iconsCache[cacheKey]) return iconsCache[cacheKey];
// DB에서 해당 아이템 검색 (icon이 있으면 바로 반환)
let table = type === "entity" ? "entities" : "items";
const [rows] = await dbPool.query(
`SELECT icon, mod_id FROM ${table} WHERE name = ? LIMIT 1`,
[name]
);
if (rows.length > 0 && rows[0].icon) {
// DB에 icon이 이미 있으면 캐시에 저장하고 반환
setIconCache(cacheKey, rows[0].icon);
return rows[0].icon;
}
// DB에 없거나 icon이 없으면 온디맨드 다운로드 (minecraft 아이템만)
const modId = rows.length > 0 ? rows[0].mod_id : "minecraft";
// minecraft가 아닌 모드 아이템은 외부에서 다운로드 불가
if (modId !== "minecraft") {
return null;
}
// 소스 URL 결정
let sourceUrl;
let s3Key;
if (type === "entity") {
const mhfUuid = entityToMHF[name];
if (mhfUuid) {
sourceUrl = `https://mc-heads.net/avatar/${mhfUuid}/64`;
} else {
sourceUrl = `https://mc.nerothe.com/img/1.21/minecraft_${name}_spawn_egg.png`;
}
s3Key = `icons/entities/${modId}_${name}.png`;
} else {
// item과 block 모두 icons/items에 저장
sourceUrl = `https://mc.nerothe.com/img/1.21/minecraft_${name}.png`;
s3Key = `icons/items/${modId}_${name}.png`;
}
try {
const imageData = await downloadImage(sourceUrl);
const publicUrl = await uploadToS3(s3Config.bucket, s3Key, imageData);
// DB 업데이트
await dbPool.query(
`UPDATE ${table} SET icon = ? WHERE name = ? AND mod_id = ?`,
[publicUrl, name, modId]
);
// 캐시 업데이트
setIconCache(cacheKey, publicUrl);
console.log(`[Icon] 저장 완료: ${cacheKey}${publicUrl}`);
return publicUrl;
} catch (error) {
console.error(`[Icon] 다운로드 실패: ${cacheKey} - ${error.message}`);
return null;
}
}
export { getIconUrl };