import { sql } from '../sql.js'; /** * @typedef {Object} PriceSnapshotRow * @property {number} cardId * @property {string} source * @property {string} [priceType] * @property {string} [currency] * @property {number} amount * @property {string | Date} [recordedAt] */ /** * Upsert daily price snapshots (idempotent per card/source/type/currency/day). * * @param {PriceSnapshotRow[]} rows * @returns {Promise} rows written */ export async function recordPriceSnapshots(rows) { if (!rows?.length) return 0; let written = 0; for (const row of rows) { if (row.cardId == null || row.amount == null || Number.isNaN(Number(row.amount))) { continue; } const priceType = row.priceType ?? 'market'; const currency = row.currency ?? 'USD'; const recordedAt = row.recordedAt instanceof Date ? row.recordedAt.toISOString().slice(0, 10) : row.recordedAt ?? new Date().toISOString().slice(0, 10); await sql` INSERT INTO card_price_history (card_id, source, price_type, currency, amount, recorded_at) VALUES ( ${row.cardId}, ${row.source}, ${priceType}, ${currency}, ${Number(row.amount)}, ${recordedAt} ) ON CONFLICT (card_id, source, price_type, currency, recorded_at) DO UPDATE SET amount = EXCLUDED.amount `; written += 1; } return written; } /** * @param {number} cardId * @param {{ days?: number }} [options] * @returns {Promise>} */ export async function getPriceHistoryPoints(cardId, { days = 90 } = {}) { const cappedDays = Math.min(Math.max(Number(days) || 90, 1), 365); const result = await sql` SELECT recorded_at, amount, source, price_type, currency FROM card_price_history WHERE card_id = ${cardId} AND recorded_at >= CURRENT_DATE - ${cappedDays}::integer ORDER BY recorded_at ASC `; return result.rows.map((row) => ({ date: row.recorded_at instanceof Date ? row.recorded_at.toISOString().slice(0, 10) : String(row.recorded_at).slice(0, 10), amount: parseFloat(row.amount), source: row.source, priceType: row.price_type, currency: row.currency, })); }