Files
uniare.sori.studio/integrations/uadeck-deck-client.js
zenn a281d731a1 v4.0.0: UNIARE 리브랜딩·레이아웃·덱 UX 개선
제품명·헤더 로고 이미지 통일, 덱 이름 변경·조회 수 제거, 썸네일 홀드 미리보기를 누르는 동안만 표시하도록 수정하고 Tailwind CSS를 재빌드했다.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 11:19:07 +09:00

131 lines
3.9 KiB
JavaScript

/**
* UNIARE 덱 API 클라이언트 (옆 프로젝트 연동용)
*
* @example
* const client = new UaDeckDeckClient({ baseUrl: 'https://uniare.sori.studio/api/v1' });
* const deck = await client.fetchDeck('x7Kp9m2Q');
* deck.cards.forEach(c => console.log(c.number, c.count, c.imgSrc));
*/
class UaDeckDeckClient {
/**
* @param {object} options
* @param {string} options.baseUrl API 베이스 (예: https://uniare.sori.studio/api/v1)
* @param {string} [options.imageBase] 이미지 호스트 (기본: baseUrl에서 /api/v1 제거)
*/
constructor(options = {}) {
this.baseUrl = (options.baseUrl || '/api/v1').replace(/\/$/, '');
this.imageBase = options.imageBase || this.baseUrl.replace(/\/api\/v1$/, '');
}
/**
* 공유 코드로 덱 조회
* @param {string} shareCode
* @returns {Promise<UaDeckDeck>}
*/
async fetchDeck(shareCode) {
const res = await fetch(`${this.baseUrl}/decks/${encodeURIComponent(shareCode)}`);
const data = await res.json();
if (!res.ok) {
throw new Error(data.error || `덱 조회 실패 (${res.status})`);
}
return this._normalizeDeck(data);
}
/**
* URL에서 shareCode 추출
* @param {string} url 예: https://uniare.sori.studio/d/x7Kp9m2Q 또는 ?d=x7Kp9m2Q
* @returns {string|null}
*/
static parseShareCode(url) {
try {
const u = new URL(url, 'https://placeholder.local');
const fromQuery = u.searchParams.get('d');
if (fromQuery) return fromQuery;
const match = u.pathname.match(/\/d\/([A-Za-z0-9]+)/);
return match ? match[1] : null;
} catch {
return null;
}
}
/**
* URL 또는 코드로 덱 로드
* @param {string} urlOrCode
* @returns {Promise<UaDeckDeck>}
*/
async fetchFromUrl(urlOrCode) {
const code = UaDeckDeckClient.parseShareCode(urlOrCode) || urlOrCode;
return this.fetchDeck(code);
}
/**
* 카드 이미지 전체 URL
* @param {string} imgSrc API 응답의 imgSrc (예: /ua36/UA36BT_MCR-1-052.png)
* @returns {string}
*/
imageUrl(imgSrc) {
return `${this.imageBase}/images${imgSrc.startsWith('/') ? imgSrc : `/${imgSrc}`}`;
}
/**
* @param {object} raw API 응답
* @returns {UaDeckDeck}
*/
_normalizeDeck(raw) {
return {
shareCode: raw.shareCode,
shareType: raw.shareType,
series: raw.series,
seriesName: raw.seriesName,
name: raw.name,
totalCards: raw.totalCards,
author: raw.author,
createdAt: raw.createdAt,
updatedAt: raw.updatedAt,
viewCount: raw.viewCount,
cards: (raw.cards || []).map((c) => ({
number: c.number,
count: c.count,
key: Boolean(c.key),
imgSrc: c.imgSrc,
imageUrl: this.imageUrl(c.imgSrc),
color: c.color,
rarity: c.rarity,
trigger: c.trigger,
parallel: Boolean(c.parallel),
})),
};
}
}
if (typeof module !== 'undefined' && module.exports) {
module.exports = { UaDeckDeckClient };
}
if (typeof window !== 'undefined') {
window.UaDeckDeckClient = UaDeckDeckClient;
}
/**
* @typedef {object} UaDeckCard
* @property {string} number
* @property {number} count
* @property {boolean} key
* @property {string} imgSrc
* @property {string} imageUrl
* @property {string} color
* @property {string} rarity
* @property {string|null} trigger
* @property {boolean} parallel
*/
/**
* @typedef {object} UaDeckDeck
* @property {string} shareCode
* @property {string} shareType
* @property {string} series
* @property {string} seriesName
* @property {string} name
* @property {number} totalCards
* @property {UaDeckCard[]} cards
*/