131 lines
3.9 KiB
TypeScript

// 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" } },
);
}
},
});