// routes/api/import/incomes.ts // Import incomes from JSON; skip items that already exist (by id or by content). import { define } from "../../../utils.ts"; import { createIncome, getIncomesByUser, } from "../../../services/financhalServices.ts"; import { SessionCookie } from "../../auth/callback.ts"; type ImportItem = { id?: string; source?: string | null; amount?: number | null; frequency?: string | null; received_date?: string | number | null; }; function signature(inc: { source?: string | null; amount?: number | null; frequency?: string | null; received_date?: number | null; }): string { const r = inc.received_date ?? 0; return `${inc.source ?? ""}|${inc.amount ?? 0}|${inc.frequency ?? ""}|${r}`; } export const handler = define.handlers({ async POST(ctx) { try { const stateData: SessionCookie = ctx.state.data; const user_id = stateData?.user_id; if (!user_id) { return new Response( JSON.stringify({ error: "Unauthorized" }), { status: 401, headers: { "Content-Type": "application/json" } }, ); } const contentType = ctx.req.headers.get("content-type") ?? ""; if (!contentType.includes("application/json")) { return new Response( JSON.stringify({ error: "Content-Type must be application/json" }), { status: 400, headers: { "Content-Type": "application/json" } }, ); } const body = await ctx.req.json(); const raw = Array.isArray(body) ? body : body?.incomes; if (!Array.isArray(raw)) { return new Response( JSON.stringify({ error: "Body must be an array or { incomes: [] }", }), { status: 400, headers: { "Content-Type": "application/json" } }, ); } const existing = await getIncomesByUser(user_id); const existingIds = new Set(existing.map((i) => i.id)); const existingSigs = new Set( existing.map((i) => signature({ source: i.source, amount: i.amount, frequency: i.frequency, received_date: i.received_date, }) ), ); let added = 0; for (const item of raw as ImportItem[]) { const id = typeof item.id === "string" ? item.id : undefined; if (id && existingIds.has(id)) continue; const amount = item.amount != null ? Number(item.amount) : null; const receivedRaw = item.received_date; let received_date: number; if (receivedRaw == null || receivedRaw === "") { received_date = Date.now(); } else if (typeof receivedRaw === "number") { received_date = receivedRaw; } else { received_date = Date.parse(String(receivedRaw).replace(/Z?$/, "T00:00:00")) || Date.now(); } const src = item.source != null ? String(item.source) : null; const freq = (item.frequency != null ? String(item.frequency) : "") || "monthly"; const sig = signature({ source: src, amount, frequency: freq, received_date, }); if (existingSigs.has(sig)) continue; await createIncome( user_id, src, amount != null && amount > 0 ? amount : null, freq, received_date, "", ); added++; existingSigs.add(sig); } return new Response( JSON.stringify({ added, total: raw.length, skipped: raw.length - added, }), { status: 200, headers: { "Content-Type": "application/json" } }, ); } catch (e) { console.error("Import incomes error:", e); return new Response( JSON.stringify({ error: "Failed to import incomes" }), { status: 500, headers: { "Content-Type": "application/json" } }, ); } }, });