// === 상품별 사용자 메타데이터 저장소 === // 네이버 API에 저장 못하는 사용자 자체 정보 (매입처, 모델 등) 보관. // 우선 localStorage 백엔드, 추후 Supabase 자격증명 입력 시 자동 전환. // // 사용: // await window.MetaStore.get("12345") → { supplier: "...", model: "..." } | null // await window.MetaStore.set("12345", { supplier: "신성" }) // await window.MetaStore.getMany(["12345","99"]) → Map(originProductNo → meta) // await window.MetaStore.list() → Array<{originProductNo, supplier, model, updated_at}> (function () { const LOCAL_KEY = "smartfarm.product_meta.v1"; const CRED_KEY = "smartfarm.creds.v1"; // 기존 자격증명 키 재사용 function _loadCreds() { try { return JSON.parse(localStorage.getItem(CRED_KEY) || "{}"); } catch { return {}; } } function _supabaseConfigured() { const c = _loadCreds(); return Boolean(c.supabase_url && c.supabase_anon_key); } // ---- localStorage 백엔드 ---- function _localLoad() { try { return JSON.parse(localStorage.getItem(LOCAL_KEY) || "{}"); } catch { return {}; } } function _localSave(obj) { localStorage.setItem(LOCAL_KEY, JSON.stringify(obj)); } async function localGet(originProductNo) { const all = _localLoad(); return all[originProductNo] || null; } async function localSet(originProductNo, patch) { const all = _localLoad(); const existing = all[originProductNo] || {}; const merged = { ...existing, ...patch, updated_at: new Date().toISOString() }; all[originProductNo] = merged; _localSave(all); return merged; } async function localGetMany(ids) { const all = _localLoad(); const m = new Map(); ids.forEach(id => { if (all[id]) m.set(String(id), all[id]); }); return m; } async function localList() { const all = _localLoad(); return Object.entries(all).map(([k, v]) => ({ originProductNo: k, ...v })); } // ---- Supabase 백엔드 (REST API 직접 호출) ---- function _supaHeaders() { const c = _loadCreds(); return { "apikey": c.supabase_anon_key, "Authorization": "Bearer " + c.supabase_anon_key, "Content-Type": "application/json", "Prefer": "return=representation", }; } function _supaUrl(path) { const c = _loadCreds(); return c.supabase_url.replace(/\/+$/, "") + "/rest/v1" + path; } async function supaGet(originProductNo) { const r = await fetch( _supaUrl(`/naver_smartstore_product_meta?origin_product_no=eq.${encodeURIComponent(originProductNo)}&select=*`), { headers: _supaHeaders() } ); if (!r.ok) throw new Error("Supabase get 실패 HTTP " + r.status); const rows = await r.json(); return rows[0] || null; } async function supaSet(originProductNo, patch) { // Upsert (있으면 update, 없으면 insert) — on_conflict const body = { origin_product_no: String(originProductNo), ...patch, updated_at: new Date().toISOString() }; const r = await fetch(_supaUrl(`/naver_smartstore_product_meta?on_conflict=origin_product_no`), { method: "POST", headers: { ..._supaHeaders(), "Prefer": "resolution=merge-duplicates,return=representation" }, body: JSON.stringify(body), }); if (!r.ok) { const t = await r.text(); throw new Error("Supabase set 실패 HTTP " + r.status + " " + t.slice(0, 200)); } const rows = await r.json(); return rows[0]; } async function supaGetMany(ids) { if (ids.length === 0) return new Map(); // PostgREST in operator: ?origin_product_no=in.(1,2,3) const list = ids.map(id => `"${id}"`).join(","); const r = await fetch(_supaUrl(`/naver_smartstore_product_meta?origin_product_no=in.(${list})&select=*`), { headers: _supaHeaders(), }); if (!r.ok) throw new Error("Supabase getMany 실패 HTTP " + r.status); const rows = await r.json(); const m = new Map(); rows.forEach(r => m.set(String(r.origin_product_no), r)); return m; } async function supaList() { const r = await fetch(_supaUrl(`/naver_smartstore_product_meta?select=*&order=updated_at.desc`), { headers: _supaHeaders() }); if (!r.ok) throw new Error("Supabase list 실패 HTTP " + r.status); return r.json(); } // ---- Backend 자동 선택 ---- function _backend() { return _supabaseConfigured() ? "supabase" : "local"; } async function get(originProductNo) { if (!originProductNo) return null; return _backend() === "supabase" ? supaGet(originProductNo).catch(err => { console.warn("[MetaStore.get supabase 실패, local fallback]", err); return localGet(originProductNo); }) : localGet(originProductNo); } async function set(originProductNo, patch) { if (!originProductNo) throw new Error("originProductNo 필수"); // 항상 local에 동시 저장 (Supabase 실패 시 대비 + offline) localSet(originProductNo, patch); if (_backend() === "supabase") { try { return await supaSet(originProductNo, patch); } catch (err) { console.warn("[MetaStore.set supabase 실패]", err); } } return localGet(originProductNo); } async function getMany(ids) { if (!ids || ids.length === 0) return new Map(); if (_backend() === "supabase") { try { return await supaGetMany(ids); } catch (err) { console.warn("[MetaStore.getMany supabase 실패, local fallback]", err); } } return localGetMany(ids); } async function list() { if (_backend() === "supabase") { try { return await supaList(); } catch (err) { console.warn("[MetaStore.list supabase 실패, local fallback]", err); } } return localList(); } function backendInfo() { return { backend: _backend(), supabaseConfigured: _supabaseConfigured(), }; } window.MetaStore = { get, set, getMany, list, backendInfo }; })();