top of page

// 【Fish租平台|獨立 Ragic API 工具】 // 用途:租平台獨立後端使用,不依賴既有 ragicApi.js。 // 放置位置:WIX > Backend > fishRentRagicApi.js import { fetch } from 'wix-fetch'; import { getSecret } from 'wix-secrets-backend'; import { RENT_PLATFORM, RENT_SHEETS, RENT_FIELD_IDS, RENT_FIELD_LABELS, RENT_TABLE_POLICIES } from 'backend/fishRentConfig'; async function getRentApiKey() { // 第一階段:所有店家共用平台資料庫,每筆資料用 tenantId 隔離。 // 第二階段:RENT_PLATFORM.tenantDatabaseReady 開啟後,再依店家串接設定切到每店獨立資料庫。 try { const key = await getSecret(RENT_PLATFORM.secretName); if (key) return key; } catch (error) { // 新獨立 secret 尚未建立時,允許暫用原本 RAGIC_API_KEY 驗收。 } const fallback = await getSecret(RENT_PLATFORM.fallbackSecretName); if (!fallback) { throw new Error(`Missing WIX secret: ${RENT_PLATFORM.secretName}`); } return fallback; } function clean(value) { return String(value ?? '').trim(); } function buildUrl(sheetPath, params = {}) { const url = new URL(`${RENT_PLATFORM.baseUrl}${sheetPath}`); url.searchParams.set('v', '3'); url.searchParams.set('api', ''); Object.keys(params || {}).forEach((key) => { if (params[key] !== undefined && params[key] !== null && params[key] !== '') { url.searchParams.set(key, String(params[key])); } }); return url.toString(); } async function rentRequest(sheetPath, options = {}) { const apiKey = await getRentApiKey(); const headers = { Authorization: `Basic ${apiKey}` }; if (options.body) headers['Content-Type'] = 'application/json'; const response = await fetch(buildUrl(sheetPath, options.params), { method: options.method || 'GET', headers, body: options.body ? JSON.stringify(options.body) : undefined }); const text = await response.text(); let data = {}; try { data = text ? JSON.parse(text) : {}; } catch (error) { data = { raw: text }; } if (!response.ok) { throw new Error(`Ragic API error ${response.status}: ${text.slice(0, 800)}`); } return data; } export function rentSheetPath(tableKey) { const sheetPath = RENT_SHEETS[tableKey]; if (!sheetPath) throw new Error(`Fish租平台尚未設定 Ragic 表單:${tableKey}`); return sheetPath; } export function rentTablePolicy(tableKey) { const policy = RENT_TABLE_POLICIES[tableKey]; if (!policy) throw new Error(`Fish租平台未開放此資料表:${tableKey}`); return policy; } export function rentFieldKey(tableKey, logicalKey) { const fieldId = RENT_FIELD_IDS?.[tableKey]?.[logicalKey]; if (fieldId) return String(fieldId); const label = RENT_FIELD_LABELS?.[tableKey]?.[logicalKey]; if (!label) throw new Error(`Missing Fish租平台 field config: ${tableKey}.${logicalKey}`); return label; } export function rentFieldLabel(tableKey, logicalKey) { const label = RENT_FIELD_LABELS?.[tableKey]?.[logicalKey]; if (!label) throw new Error(`Missing Fish租平台 field label: ${tableKey}.${logicalKey}`); return label; } function hasFieldIds(tableKey) { return Boolean(RENT_FIELD_IDS?.[tableKey] && Object.keys(RENT_FIELD_IDS[tableKey]).length); } function writeNamingFor(tableKey, values = {}) { return hasFieldIds(tableKey) ? 'EID' : 'FNAME'; } function rentWriteFieldKey(tableKey, logicalKey, naming) { if (naming === 'EID') return rentFieldKey(tableKey, logicalKey); return rentFieldLabel(tableKey, logicalKey); } export function rentToRagicPayload(tableKey, values = {}, naming = writeNamingFor(tableKey, values)) { return Object.keys(values || {}).reduce((payload, logicalKey) => { const value = values[logicalKey]; if (value === undefined) return payload; if (naming === 'EID' && !RENT_FIELD_IDS?.[tableKey]?.[logicalKey]) return payload; payload[rentWriteFieldKey(tableKey, logicalKey, naming)] = value; return payload; }, {}); } export function rentReadField(tableKey, row = {}, logicalKey) { const fieldId = RENT_FIELD_IDS?.[tableKey]?.[logicalKey]; const label = RENT_FIELD_LABELS?.[tableKey]?.[logicalKey]; return row?.[fieldId] ?? row?.[label] ?? ''; } export function rentFromRagicRecord(tableKey, row = {}, logicalKeys = []) { return logicalKeys.reduce((out, logicalKey) => { out[logicalKey] = rentReadField(tableKey, row, logicalKey); return out; }, { ragicId: rentRowId(row) }); } export function rentRowId(row = {}) { return row?._ragicId || row?._id || row?.id || ''; } export function rentRowsObjectToArray(result) { if (Array.isArray(result)) { return result.filter((row) => row && typeof row === 'object'); } return Object.keys(result || {}) .filter((key) => !String(key).startsWith('_')) .map((key) => ({ _ragicId: result[key]?._ragicId || result[key]?._id || result[key]?.id || key, ...result[key] })) .filter((row) => row && typeof row === 'object'); } export async function rentListRows(tableKey, params = {}) { return rentRequest(rentSheetPath(tableKey), { method: 'GET', params: { naming: 'FNAME', ...params } }); } export async function rentListRowsSafe(tableKey, params = {}) { try { return rentRowsObjectToArray(await rentListRows(tableKey, params)); } catch (error) { return []; } } export async function rentCreateRow(tableKey, values) { rentTablePolicy(tableKey); const naming = writeNamingFor(tableKey, values); return rentRequest(rentSheetPath(tableKey), { method: 'POST', params: { naming }, body: rentToRagicPayload(tableKey, values, naming) }); } export async function rentUpdateRow(tableKey, ragicId, values) { const id = clean(ragicId); if (!id) throw new Error('缺少 Ragic 資料列 ID'); rentTablePolicy(tableKey); const naming = writeNamingFor(tableKey, values); return rentRequest(`${rentSheetPath(tableKey)}/${id}`, { method: 'PATCH', params: { naming }, body: rentToRagicPayload(tableKey, values, naming) }); } export async function rentDeleteRow(tableKey, ragicId) { const id = clean(ragicId); if (!id) throw new Error('缺少 Ragic 資料列 ID'); rentTablePolicy(tableKey); return rentRequest(`${rentSheetPath(tableKey)}/${id}`, { method: 'DELETE' }); } export async function rentFindByField(tableKey, logicalKey, value) { const expected = clean(value); if (!expected) return []; const label = rentFieldLabel(tableKey, logicalKey); try { const data = await rentListRows(tableKey, { where: `${label},eq,${expected}` }); const filtered = rentRowsObjectToArray(data) .filter((row) => clean(rentReadField(tableKey, row, logicalKey)).toLowerCase() === expected.toLowerCase()); if (filtered.length) return filtered; } catch (error) { // where 語法在中文欄位或部分 Ragic naming 模式可能失敗;下方會改走全表比對。 } const rows = rentRowsObjectToArray(await rentListRows(tableKey)); return rows.filter((row) => clean(rentReadField(tableKey, row, logicalKey)).toLowerCase() === expected.toLowerCase()); } export async function rentSecretHealth() { try { await getRentApiKey(); return { ok: true, secretName: RENT_PLATFORM.secretName, fallbackSecretName: RENT_PLATFORM.fallbackSecretName }; } catch (error) { return { ok: false, secretName: RENT_PLATFORM.secretName, fallbackSecretName: RENT_PLATFORM.fallbackSecretName, message: error.message }; } }

bottom of page