v4.0.0: 회원·덱 API, 인증 UI, 프로필 및 레이아웃 통일

Express/SQLite API와 Docker 구성을 추가하고 로그인·덱 저장·공개 덱·관리자·프로필 화면을 구현했다. 결과 화면 저장 UX, 헤더 프로필, 애드센스용 app-shell 레이아웃(1280px) 및 문서를 반영했다.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-22 16:59:36 +09:00
parent f8c954f24b
commit dbf02241e2
56 changed files with 6732 additions and 183 deletions

View File

@@ -0,0 +1,65 @@
import { pathToFileURL } from 'url';
import path from 'path';
/** @type {Map<string, Map<string, object>>} */
const cache = new Map();
/**
* 시리즈 카드 맵 로드
* @param {string} seriesKey
* @returns {Promise<Map<string, object>>}
*/
export async function getCardMap(seriesKey) {
if (cache.has(seriesKey)) {
return cache.get(seriesKey);
}
const datasPath = process.env.DATAS_PATH || path.join(process.cwd(), '..', 'datas');
const filePath = path.join(datasPath, `${seriesKey}.js`);
let mod;
try {
mod = await import(pathToFileURL(filePath).href);
} catch {
throw new Error(`시리즈 데이터를 찾을 수 없습니다: ${seriesKey}`);
}
const byNumber = new Map();
for (const card of mod.cardList) {
byNumber.set(card.Number, card);
}
cache.set(seriesKey, byNumber);
return byNumber;
}
/**
* 저장된 덱 카드를 풀 카드 객체로 합성
* @param {string} seriesKey
* @param {Array<{number: string, count: number, key: boolean}>} cards
* @returns {Promise<{cards: object[], totalCards: number}>}
*/
export async function resolveDeckCards(seriesKey, cards) {
const map = await getCardMap(seriesKey);
const resolved = [];
let totalCards = 0;
for (const item of cards) {
const base = map.get(item.number);
if (!base) continue;
totalCards += item.count;
resolved.push({
number: base.Number,
count: item.count,
key: item.key,
imgSrc: base.imgSrc,
color: base.color,
rarity: base.rarity,
trigger: base.trigger ?? null,
parallel: Boolean(base.parallel),
});
}
return { cards: resolved, totalCards };
}