// src/api.js // 프론트와 API가 동일 도메인(nginx 프록시)이므로 항상 상대 경로 사용. // 절대 URL(VITE_API_BASE)은 Mixed Content를 유발하므로 사용하지 않음. const toApiUrl = (path) => path; export async function apiGet(path) { const res = await fetch(toApiUrl(path), { headers: { "Accept": "application/json" }, }); if (!res.ok) { const text = await res.text().catch(() => ""); throw new Error(`HTTP ${res.status} ${res.statusText}: ${text}`); } return res.json(); } export async function apiDelete(path) { const res = await fetch(toApiUrl(path), { method: "DELETE" }); if (!res.ok) { const text = await res.text().catch(() => ""); throw new Error(`HTTP ${res.status} ${res.statusText}: ${text}`); } return res.json(); } export async function apiPost(path, body) { const res = await fetch(toApiUrl(path), { method: "POST", headers: { "Accept": "application/json", ...(body ? { "Content-Type": "application/json" } : {}), }, body: body ? JSON.stringify(body) : undefined, }); if (!res.ok) { const text = await res.text().catch(() => ""); throw new Error(`HTTP ${res.status} ${res.statusText}: ${text}`); } return res.json(); } export async function apiPut(path, body) { const res = await fetch(toApiUrl(path), { method: "PUT", headers: { "Accept": "application/json", ...(body ? { "Content-Type": "application/json" } : {}), }, body: body ? JSON.stringify(body) : undefined, }); if (!res.ok) { const text = await res.text().catch(() => ""); throw new Error(`HTTP ${res.status} ${res.statusText}: ${text}`); } return res.json(); } export function getLatest() { return apiGet("/api/lotto/latest"); } export function getStats() { return apiGet("/api/lotto/stats"); } export function recommend(params) { const qs = new URLSearchParams({ recent_window: String(params.recent_window), recent_weight: String(params.recent_weight), avoid_recent_k: String(params.avoid_recent_k), }); return apiGet(`/api/lotto/recommend?${qs.toString()}`); } export function getHistory(limit = 30, offset = 0) { return apiGet(`/api/history?limit=${limit}&offset=${offset}`); } export function deleteHistory(id) { return apiDelete(`/api/history/${id}`); } // ── 시뮬레이션 관련 API ────────────────────────────────────────────────────── export function getBestPicks(limit = 20) { return apiGet(`/api/lotto/best?limit=${limit}`); } export function getAnalysis() { return apiGet('/api/lotto/analysis'); } export function triggerSimulate(nCandidates = 20000, topK = 100, bestN = 20) { const qs = new URLSearchParams({ n_candidates: String(nCandidates), top_k: String(topK), best_n: String(bestN), }); return apiPost(`/api/admin/simulate?${qs.toString()}`); } export function getStockNews(limit = 20, category) { const qs = new URLSearchParams({ limit: String(limit) }); if (category) { qs.set("category", category); } return apiGet(`/api/stock/news?${qs.toString()}`); } export function getStockIndices() { return apiGet("/api/stock/indices"); } export function getTradeBalance() { return apiGet("/api/trade/balance"); } export function createTradeOrder(payload) { return apiPost("/api/trade/order", payload); } // ── 포트폴리오 (수동 입력) API ────────────────────────────────────────────── export function getPortfolio() { return apiGet("/api/portfolio"); } export function addPortfolio(item) { return apiPost("/api/portfolio", item); } export function updatePortfolio(id, fields) { return apiPut(`/api/portfolio/${id}`, fields); } export function deletePortfolio(id) { return apiDelete(`/api/portfolio/${id}`); } // ── 예수금 API ─────────────────────────────────────────────────────────────── export function upsertCash(broker, cash) { return apiPut('/api/portfolio/cash', { broker, cash }); } export function deleteCash(broker) { return apiDelete(`/api/portfolio/cash/${encodeURIComponent(broker)}`); } // ── 시장 심리 지표 API ──────────────────────────────────────────────────────── // CNN Fear & Greed Index (개발: vite proxy /ext/feargreed, 프로덕션: nginx proxy 필요) export async function getFearAndGreed() { const res = await fetch('/ext/feargreed', { headers: { Accept: 'application/json' } }); if (!res.ok) throw new Error(`HTTP ${res.status}`); return res.json(); } // VIX 지수 (Yahoo Finance 공개 API) export async function getVix() { const res = await fetch('/ext/vix', { headers: { Accept: 'application/json' } }); if (!res.ok) throw new Error(`HTTP ${res.status}`); const data = await res.json(); const price = data?.chart?.result?.[0]?.meta?.regularMarketPrice; if (price === undefined || price === null) throw new Error('VIX 데이터 없음'); return { value: Math.round(price * 100) / 100 }; } // ── TODO API ───────────────────────────────────────────────────────────────── export function getTodos() { return apiGet('/api/todos'); } export function addTodo(data) { return apiPost('/api/todos', data); } export function updateTodo(id, data) { return apiPut(`/api/todos/${id}`, data); } export function deleteTodo(id) { return apiDelete(`/api/todos/${id}`); } export function clearTodos() { return apiDelete('/api/todos/done'); }