// supabase-client.jsx — Supabase client + auth + history // Loaded BEFORE App.jsx. Exposes window.api with all backend operations. (function () { // ── 設定 (Claude Code 部署時請改這兩個值) ── const SUPABASE_URL = window.SUPABASE_URL || 'https://celebread.zeabur.app'; const SUPABASE_ANON_KEY = window.SUPABASE_ANON_KEY || 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE'; const REST = SUPABASE_URL.replace(/\/$/, '') + '/rest/v1'; const headers = (extra = {}) => ({ 'apikey': SUPABASE_ANON_KEY, 'Authorization': 'Bearer ' + SUPABASE_ANON_KEY, 'Content-Type': 'application/json', ...extra, }); // SHA-256 密碼 hash (Web Crypto API) async function hashPassword(pw) { const enc = new TextEncoder().encode(pw); const buf = await crypto.subtle.digest('SHA-256', enc); return Array.from(new Uint8Array(buf)) .map(b => b.toString(16).padStart(2, '0')).join(''); } async function req(path, opts = {}) { const res = await fetch(REST + path, { ...opts, headers: headers(opts.headers || {}), }); if (!res.ok) { const text = await res.text(); throw new Error(`API ${res.status}: ${text}`); } if (res.status === 204) return null; return res.json(); } // ── Auth ── const SESSION_KEY = 'celebread_session_v1'; async function login(username, password) { const ph = await hashPassword(password); const rows = await req(`/users?username=eq.${encodeURIComponent(username)}&select=*`); if (!rows.length) throw new Error('帳號不存在'); const user = rows[0]; if (user.password_hash !== ph) throw new Error('密碼錯誤'); const session = { id: user.id, username: user.username, display_name: user.display_name, role: user.role, }; localStorage.setItem(SESSION_KEY, JSON.stringify(session)); return session; } function getSession() { try { return JSON.parse(localStorage.getItem(SESSION_KEY)) || null; } catch { return null; } } function logout() { localStorage.removeItem(SESSION_KEY); } // ── Users ── async function listUsers() { return req('/users?select=id,username,display_name,role,created_at&order=created_at.asc'); } async function createUser({ username, password, display_name, role }) { const password_hash = await hashPassword(password); return req('/users', { method: 'POST', headers: { 'Prefer': 'return=representation' }, body: JSON.stringify({ username, password_hash, display_name, role }), }); } async function deleteUser(id) { return req(`/users?id=eq.${id}`, { method: 'DELETE' }); } async function updatePassword(id, password) { const password_hash = await hashPassword(password); return req(`/users?id=eq.${id}`, { method: 'PATCH', body: JSON.stringify({ password_hash }), }); } // ── Production logs ── async function logProduction(payload) { return req('/production_logs', { method: 'POST', headers: { 'Prefer': 'return=representation' }, body: JSON.stringify(payload), }); } async function listLogs({ limit = 200 } = {}) { return req(`/production_logs?select=*&order=created_at.desc&limit=${limit}`); } async function deleteLog(id) { return req(`/production_logs?id=eq.${id}`, { method: 'DELETE' }); } window.api = { SUPABASE_URL, login, logout, getSession, listUsers, createUser, deleteUser, updatePassword, logProduction, listLogs, deleteLog, hashPassword, }; })();